OAuth 2.0 implementálása Windows alkalmazásokban

A Windows App SDK OAuth2Manager lehetővé teszi, hogy az olyan asztali alkalmazások, mint a WinUI 3, zökkenőmentesen végezhesse el az OAuth 2.0-hitelesítést Windows. Az OAuth2Manager API nem biztosít API-kat az implicit kéréshez és az erőforrás-tulajdonosi jelszó hitelesítő adataihoz, mivel az ezzel járó biztonsági problémák miatt. Használja az engedélyezési kód megadásának típusát a Code Exchange (PKCE) ellenőrző kulcsával. További információ: PKCE RFC.

Megjegyzés:

OAuth2Manager olyan általános OAuth 2.0-folyamatokhoz készült, amelyekhez bármilyen identitásszolgáltató (GitHub, Google, egyéni stb.) tartozik, és mindig a rendszerböngészőt használja az engedélyezési lépéshez. Ha kifejezetten Microsoft fiókokkal vagy Microsoft Entra ID (munkahelyi/iskolai) fiókkal szeretne bejelentkezni, csendes SSO-val – a Windows már bejelentkezett fiókkal, böngészőüzenet nélkül –, használja az MSAL.NET-et a Web Account Manager (WAM) közvetítővel helyette. A Web Account Manager Windows Hello integrációs és feltételes hozzáférési támogatást is biztosít, amelyet az OAuth2Manager nem.

Az OAuth2Manager API a Windows App SDK-n belül

A OAuth2Manager API for Windows App SDK egy olyan egyszerűsített megoldást kínál, amely megfelel a fejlesztők elvárásainak. Zökkenőmentes OAuth 2.0-képességeket kínál, teljes körű funkciók paritásával az Windows App SDK által támogatott összes Windows platformon. Az új API szükségtelenné teszi a nehézkes kerülő megoldásokat, és leegyszerűsíti az OAuth 2.0-funkciók asztali alkalmazásokba való beépítésének folyamatát.

Az OAuth2Manager eltér a WinRT WebAuthenticationBrokerétől . Jobban követi az OAuth 2.0 ajánlott eljárásait – például a felhasználó alapértelmezett böngészőjének használatával. Az API ajánlott eljárásai az IETF (Internet Engineering Task Force) OAuth 2.0 engedélyezési keretrendszer RFC 6749-es, PKCE RFC 7636-os és OAuth 2.0-s natív alkalmazások RFC 8252-es kiadásából származnak.

OAuth 2.0-s kód példák

Egy teljes WinUI-mintaalkalmazás érhető el GitHub. Az alábbi szakaszok az OAuth2Manager API használatával a leggyakoribb OAuth 2.0-folyamatok kódrészleteit ismertetik.

Engedélyezési kód kérése

Az alábbi példa bemutatja, hogyan hajthat végre engedélyezési kódkérelmet a OAuth2Manager használatával a 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());
}

Engedélyezési kód cseréje hozzáférési tokenre

Az alábbi példa bemutatja, hogyan cserélhetünk egy engedélyezési kódot egy hozzáférési tokenre a OAuth2Manager használatával.

A PKCE-t használó nyilvános ügyfelek (például natív asztali alkalmazások) esetében ne tartalmazzon titkos ügyfélkulcsot. A PKCE-kód hitelesítője ehelyett a biztonságot biztosítja:

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

Az ügyféltitokkal rendelkező bizalmas ügyfelek (például webalkalmazások vagy szolgáltatások) esetében adja meg a ClientAuthentication paramétert:

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

Access-jogkivonat frissítése

Az alábbi példa bemutatja, hogyan frissíthet access tokent a OAuth2ManagerRefreshTokenAsync metódusával.

A PKCE-t használó nyilvános ügyfelek esetében hagyja ki a paramétert 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());
}

Az ügyfél titkos kóddal rendelkező bizalmas ügyfelek esetében adja meg a következő paramétert 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

Engedélyezési kérelem befejezése

Egy protokollaktiválásból származó engedélyezési kérés teljesítéséhez az alkalmazásnak kezelnie kell az AppInstance.Activated eseményt . Erre az eseményre akkor van szükség, ha az alkalmazás egyéni átirányítási logikával rendelkezik. Teljes példa érhető el GitHub.

Használja a következő kódot:

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

        DisplayUnhandledMessageToUser();
    }
}