通过


在 Windows 应用中实现 OAuth 2.0

Windows 应用 SDK中的 OAuth2Manager 使 WinUI 3 等桌面应用程序能够在 Windows 上无缝执行 OAuth 2.0 授权。 由于存在安全问题, OAuth2Manager API 不提供隐式请求和资源所有者密码凭据的 API。 将授权代码授权类型与 Proof Key for Code Exchange(PKCE)一起使用。 有关详细信息,请参阅 PKCE RFC

注释

OAuth2Manager专为具有任何标识提供者(GitHub、Google、自定义等)的常规 OAuth 2.0 流而设计,并且始终使用系统浏览器执行授权步骤。 如果你特别想通过 Microsoft 帐户或 Microsoft Entra ID(工作/学校)帐户 使用 无提示单点登录(SSO) 方式登录——即使用已登录 Windows 的帐户,而没有浏览器提示——请改用 MSAL.NET Web 帐户管理器(WAM)代理程序。 Web 帐户管理器还提供 OAuth2Manager 未提供的Windows Hello集成和条件访问支持。

Windows 应用 SDK中的 OAuth2Manager API

适用于Windows 应用 SDK的 OAuth2Manager API 提供了一种简化的解决方案,可满足开发人员的期望。 它提供无缝的 OAuth 2.0 功能,在 Windows 应用 SDK 支持的所有 Windows 平台上均实现全功能对等。 新的 API 消除了需要繁琐的解决方法,并简化了将 OAuth 2.0 功能合并到桌面应用中的过程。

OAuth2Manager 不同于 WinRT 中的 WebAuthenticationBroker。 它遵循 OAuth 2.0 最佳做法,例如,使用用户的默认浏览器。 API 的最佳做法来自 IETF(Internet 工程工作队)OAuth 2.0 授权框架 RFC 6749、PKCE RFC 7636 和 OAuth 2.0 for Native Apps RFC 8252

OAuth 2.0 代码示例

GitHub 上提供了完整的 WinUI 示例应用。 以下部分使用 OAuth2Manager API 为最常见的 OAuth 2.0 流提供代码片段。

授权代码请求

以下示例演示如何使用 Windows 应用 SDK 中的 OAuth2Manager 执行授权代码请求:

// Get the WindowId for the application window
Microsoft::UI::WindowId parentWindowId = this->AppWindow().Id();

AuthRequestParams authRequestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(L"my_client_id",
   Uri(L"my-app:/oauth-callback/"));
authRequestParams.Scope(L"user:email user:birthday");

AuthRequestResult authRequestResult = co_await OAuth2Manager::RequestAuthWithParamsAsync(parentWindowId, 
   Uri(L"https://my.server.com/oauth/authorize"), authRequestParams);
if (AuthResponse authResponse = authRequestResult.Response())
{
   //To obtain the authorization code
   //authResponse.Code();

   //To obtain the access token
   DoTokenExchange(authResponse);
}
else
{
   AuthFailure authFailure = authRequestResult.Failure();
   NotifyFailure(authFailure.Error(), authFailure.ErrorDescription());
}

Exchange访问令牌的授权代码

以下示例演示如何使用 OAuth2Manager 交换授权代码以获取访问令牌。

对于使用 PKCE 的公共客户端 (如本机桌面应用),请勿包含客户端密码。 PKCE 代码验证程序改为提供安全性:

AuthResponse authResponse = authRequestResult.Response();
TokenRequestParams tokenRequestParams = TokenRequestParams::CreateForAuthorizationCodeRequest(authResponse);

// For public clients using PKCE, do not include ClientAuthentication
TokenRequestResult tokenRequestResult = co_await OAuth2Manager::RequestTokenAsync(
    Uri(L"https://my.server.com/oauth/token"), tokenRequestParams);
if (TokenResponse tokenResponse = tokenRequestResult.Response())
{
    String accessToken = tokenResponse.AccessToken();
    String tokenType = tokenResponse.TokenType();

    // RefreshToken string null/empty when not present
    if (String refreshToken = tokenResponse.RefreshToken(); !refreshToken.empty())
    {
        // ExpiresIn is zero when not present
        DateTime expires = winrt::clock::now();
        if (String expiresIn = tokenResponse.ExpiresIn(); std::stoi(expiresIn) != 0)
        {
            expires += std::chrono::seconds(static_cast<int64_t>(std::stoi(expiresIn)));
        }
        else
        {
            // Assume a duration of one hour
            expires += std::chrono::hours(1);
        }

        //Schedule a refresh of the access token
        myAppState.ScheduleRefreshAt(expires, refreshToken);
    }

    // Use the access token for resources
    DoRequestWithToken(accessToken, tokenType);
}
else
{
    TokenFailure tokenFailure = tokenRequestResult.Failure();
    NotifyFailure(tokenFailure.Error(), tokenFailure.ErrorDescription());
}

对于具有客户端机密的 保密客户端(如 Web 应用程序或服务),请包括 ClientAuthentication 参数:

AuthResponse authResponse = authRequestResult.Response();
TokenRequestParams tokenRequestParams = TokenRequestParams::CreateForAuthorizationCodeRequest(authResponse);
ClientAuthentication clientAuth = ClientAuthentication::CreateForBasicAuthorization(L"my_client_id",
    L"my_client_secret");

TokenRequestResult tokenRequestResult = co_await OAuth2Manager::RequestTokenAsync(
    Uri(L"https://my.server.com/oauth/token"), tokenRequestParams, clientAuth);
// Handle the response as shown in the previous example

刷新访问令牌

以下示例演示如何使用 OAuth2ManagerRefreshTokenAsync 方法刷新访问令牌。

对于使用 PKCE 的公共客户端 ,请省略 ClientAuthentication 参数:

TokenRequestParams tokenRequestParams = TokenRequestParams::CreateForRefreshToken(refreshToken);

// For public clients using PKCE, do not include ClientAuthentication
TokenRequestResult tokenRequestResult = co_await OAuth2Manager::RequestTokenAsync(
    Uri(L"https://my.server.com/oauth/token"), tokenRequestParams);
if (TokenResponse tokenResponse = tokenRequestResult.Response())
{
    UpdateToken(tokenResponse.AccessToken(), tokenResponse.TokenType(), tokenResponse.ExpiresIn());

    //Store new refresh token if present
    if (String refreshToken = tokenResponse.RefreshToken(); !refreshToken.empty())
    {
        // ExpiresIn is zero when not present
        DateTime expires = winrt::clock::now();
        if (String expiresInStr = tokenResponse.ExpiresIn(); !expiresInStr.empty())
        {
            int expiresIn = std::stoi(expiresInStr);
            if (expiresIn != 0)
            {
                expires += std::chrono::seconds(static_cast<int64_t>(expiresIn));
            }
        }
        else
        {
            // Assume a duration of one hour
            expires += std::chrono::hours(1);
        }

        //Schedule a refresh of the access token
        myAppState.ScheduleRefreshAt(expires, refreshToken);
    }
}
else
{
    TokenFailure tokenFailure = tokenRequestResult.Failure();
    NotifyFailure(tokenFailure.Error(), tokenFailure.ErrorDescription());
}

对于具有客户端机密的 机密客户端 ,请包括参数 ClientAuthentication

TokenRequestParams tokenRequestParams = TokenRequestParams::CreateForRefreshToken(refreshToken);
ClientAuthentication clientAuth = ClientAuthentication::CreateForBasicAuthorization(L"my_client_id",
    L"my_client_secret");
TokenRequestResult tokenRequestResult = co_await OAuth2Manager::RequestTokenAsync(
    Uri(L"https://my.server.com/oauth/token"), tokenRequestParams, clientAuth);
// Handle the response as shown in the previous example

完成授权请求

若要完成协议激活的授权请求,应用应处理 AppInstance.Activated 事件。 当应用具有自定义重定向逻辑时,需要此事件。 GitHub 提供了完整示例。

使用以下代码:

void App::OnActivated(const IActivatedEventArgs& args)
{
    if (args.Kind() == ActivationKind::Protocol)
    {
        auto protocolArgs = args.as<ProtocolActivatedEventArgs>();
        if (OAuth2Manager::CompleteAuthRequest(protocolArgs.Uri()))
        {
            TerminateCurrentProcess();
        }

        DisplayUnhandledMessageToUser();
    }
}