Implementace OAuth 2.0 v aplikacích Windows

OAuth2Manager v Windows App SDK umožňuje desktopovým aplikacím, jako je WinUI 3, bezproblémově 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 poskytuje zjednodušené řešení, které splňuje očekávání vývojářů. Nabízí bezproblémové funkce OAuth 2.0 s úplnou paritou funkcí napříč všemi platformami Windows podporovanými Windows App SDK. Nové rozhraní API eliminuje potřebu těžkopádných alternativních řešení a zjednodušuje proces začlenění funkcí OAuth 2.0 do desktopových aplikací.

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

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