Freigeben über


Implementieren von OAuth 2.0 in Windows-Apps

Der OAuth2Manager in Windows App SDK ermöglicht Desktopanwendungen wie WinUI 3 die nahtlose OAuth 2.0-Autorisierung unter Windows. Die OAuth2Manager-API stellt keine APIs für die impliziten Anforderungs- und Ressourcenbesitzer-Kennwortanmeldeinformationen aufgrund der sicherheitsrelevanten Bedenken bereit. Verwenden Sie den Autorisierungscode-Grant-Typ mit dem Proof Key für Code Exchange (PKCE). Weitere Informationen finden Sie unter PKCE RFC.

OAuth 2.0-Hintergrund für Windows-Apps

Die Windows-Runtime (WinRT) WebAuthenticationBroker, die hauptsächlich für UWP-Apps entwickelt wurde, stellt bei der Verwendung in Desktop-Apps mehrere Herausforderungen dar. Wichtige Probleme sind die Abhängigkeit von ApplicationView, die nicht mit Desktop-App-Frameworks kompatibel ist. Daher müssen Entwickler auf Problemumgehungen zurückgreifen, die Interopschnittstellen und zusätzlichen Code umfassen, um OAuth 2.0-Funktionen in WinUI 3 und andere Desktop-Apps zu implementieren.

OAuth2Manager-API im Windows App SDK

Die OAuth2Manager-API für windows App SDK bietet eine optimierte Lösung, die den Erwartungen von Entwicklern entspricht. Es bietet nahtlose OAuth 2.0-Funktionen mit vollständiger Funktionsparität auf allen Windows-Plattformen, die vom Windows App SDK unterstützt werden. Die neue API beseitigt die Notwendigkeit umständlicher Problemumgehungen und vereinfacht den Prozess der Integration von OAuth 2.0-Funktionen in Desktop-Apps.

Der OAuth2Manager unterscheidet sich vom WebAuthenticationBroker in WinRT. Es folgt den bewährten OAuth 2.0-Methoden genauer , z. B. mithilfe des Standardbrowsers des Benutzers. Die bewährten Methoden für die API stammen aus dem IETF (Internet Engineering Task Force) OAuth 2.0 Authorization Framework RFC 6749, PKCE RFC 7636 und OAuth 2.0 für Native Apps RFC 8252.

OAuth 2.0-Codebeispiele

Eine vollständige WinUI 3-Beispiel-App ist auf GitHub verfügbar. In den folgenden Abschnitten werden Codeausschnitte für die am häufigsten verwendeten OAuth 2.0-Flüsse mithilfe der OAuth2Manager-API bereitgestellt.

Autorisierungscodeanforderung

Im folgenden Beispiel wird veranschaulicht, wie Eine Autorisierungscodeanforderung mithilfe des OAuth2Manager in Windows App SDK ausgeführt wird:

// 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-Autorisierungscode für Zugriffstoken

Das folgende Beispiel zeigt, wie Sie mithilfe des OAuth2Managers einen Autorisierungscode für ein Zugriffstoken austauschen.

Für öffentliche Clients (z. B. native Desktop-Apps), die PKCE verwenden, schließen Sie keinen geheimen Clientschlüssel ein. Die PKCE-Codeüberprüfung stellt stattdessen die Sicherheit bereit:

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());
}

Schließen Sie für vertrauliche Clients (z. B. Web-Apps oder Dienste) mit einem geheimen Clientschlüssel den ClientAuthentication Parameter ein:

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

Aktualisieren eines Zugriffstokens

Das folgende Beispiel zeigt, wie Sie ein Zugriffstoken mithilfe der RefreshTokenAsync-Methode von OAuth2Manager aktualisieren.

Für öffentliche Clients , die PKCE verwenden, lassen Sie den ClientAuthentication Parameter aus:

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());
}

Fügen Sie für vertrauliche Clients mit einem geheimen Clientschlüssel den ClientAuthentication Parameter ein:

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

Abschließen einer Autorisierungsanforderung

Um eine Autorisierungsanforderung über eine Protokollaktivierung abzuschließen, sollte Ihre App das Ereignis "AppInstance.Activated " behandeln. Dieses Ereignis ist erforderlich, wenn Ihre App über eine benutzerdefinierte Umleitungslogik verfügt. Ein vollständiges Beispiel ist auf GitHub verfügbar.

Verwenden Sie den folgenden Code:

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

        DisplayUnhandledMessageToUser();
    }
}