Implementace OAuth 2.0 v aplikacích Windows

Předpoklady

  • Windows App SDK kanál experimentální verze – OAuth2Manager není ve stabilním kanálu k dispozici. Nainstalujte předběžnou Microsoft.WindowsAppSDK verzi s příponou -experimental (v NuGetu vyberte Zahrnout předběžné verze ). Další informace najdete v tématu Nastavení vývojového prostředí.

Overview

OAuth2Manager v Windows App SDK umožňuje desktopovým aplikacím, jako je WinUI 3, provádět autorizaci OAuth 2.0 na Windows. Rozhraní OAuth2Manager API neposkytuje rozhraní API pro implicitní žádosti a přihlašovací údaje pro vlastníky prostředků kvůli souvisejícím obavám o bezpečnost. Použijte typ udělení autorizačního kódu s ověřovacím klíčem pro kód Exchange (PKCE). Další informace najdete v dokumentu PKCE RFC.

Poznámka:

OAuth2Manager je určený pro obecné toky OAuth 2.0 s libovolným zprostředkovatelem identity (GitHub, Google, vlastní atd.) a vždy používá systémový prohlížeč pro krok autorizace. Pokud se chcete konkrétně přihlásit pomocí účtů Microsoft nebo účtů Microsoft Entra ID (pracovní/školní) účty pomocí silent SSO – pomocí účtu, který je už přihlášený k Windows, bez výzvy prohlížeče – místo toho použijte MSAL.NET se zprostředkovatelem Web Account Manager (WAM). Správce webových účtů také poskytuje Windows Hello integraci a podporu podmíněného přístupu, kterou OAuth2Manager nepodporuje.

Rozhraní API OAuth2Manager v rámci Windows App SDK

Rozhraní API OAuth2Manager pro Windows App SDK zpracovává toky autorizačního kódu OAuth 2.0 pomocí PKCE pro desktopové aplikace. Funguje na všech Windows platformách podporovaných Windows App SDK a poskytuje konzistentní rozhraní pro získávání tokenů, aniž by vyžadovala alternativní řešení založená na prohlížeči třetích stran.

OAuth2Manager se liší od WebAuthenticationBroker v WinRT. Řídí se osvědčenými postupy OAuth 2.0 podrobněji – například pomocí výchozího prohlížeče uživatele. Osvědčené postupy pro rozhraní API pocházejí z IETF (Internet Engineering Task Force) OAuth 2.0 Authorization Framework RFC 6749, PKCE RFC 7636 a OAuth 2.0 pro nativní aplikace RFC 8252.

Příklady kódu OAuth 2.0

Úplná ukázková aplikace WinUI je k dispozici na GitHub. Následující části obsahují fragmenty kódu pro nejběžnější toky OAuth 2.0 pomocí rozhraní OAuth2Manager API.

Žádost o autorizační kód

Následující příklad ukazuje, jak provést žádost o autorizační kód pomocí OAuth2Manager v Windows App SDK:

// 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 autorizační kód pro přístupový token

Následující příklad ukazuje, jak pomocí OAuth2Manager vyměnit autorizační kód za přístupový token.

Pro veřejné klienty (například nativní desktopové aplikace), kteří používají PKCE, nezahrnujte tajný klíč klienta. Ověřovatel kódu PKCE místo toho poskytuje zabezpečení:

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

Important

Desktopové aplikace jsou veřejné klienty a nesmí vkládat tajné kódy klienta. Níže uvedený vzor důvěrného klienta se vztahuje pouze na komponenty na straně serveru (webové aplikace, rozhraní API, back-endové služby), které můžou bezpečně ukládat přihlašovací údaje. Nativní desktopová aplikace nemůže chránit tajný klíč klienta před extrahováním – místo toho použijte vzor veřejného klienta s pkCE uvedeným výše. Pokud vaše architektura vyžaduje tajný klíč klienta pro výměnu tokenů, proveďte tuto výměnu v back-endové službě, ne v desktopové aplikaci.

Pro klienty confidential (například web apps nebo služby), které mají tajný klíč klienta, zahrňte parametr 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

Aktualizace tokenu access

Následující příklad ukazuje, jak aktualizovat token access pomocí metody OAuth2ManagerRefreshTokenAsync.

Pro veřejné klienty , kteří používají PKCE, vynecháte ClientAuthentication parametr:

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

U důvěrných klientů , kteří mají tajný klíč klienta, uveďte ClientAuthentication parametr:

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

Dokončení žádosti o autorizaci

K dokončení žádosti o autorizaci z aktivace protokolu by vaše aplikace měla zpracovat událost AppInstance.Activated . Tato událost se vyžaduje, když má vaše aplikace vlastní logiku přesměrování. Úplný příklad je k dispozici na GitHub.

Použijte následující kód:

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

        DisplayUnhandledMessageToUser();
    }
}