Udostępnij za pośrednictwem


Implementowanie protokołu OAuth 2.0 w aplikacjach systemu Windows

OAuth2Manager w Windows App SDK umożliwia aplikacjom stacjonarnym, takim jak WinUI 3 bezproblemowe wykonywanie autoryzacji OAuth 2.0 w systemie Windows. API OAuth2Manager nie udostępnia API dla implicitnych żądań i poświadczeń hasła właściciela zasobu ze względu na względy bezpieczeństwa. Użyj typu przyznania kodu autoryzacji z kluczem weryfikującym do wymiany kodu (PKCE). Aby uzyskać więcej informacji, zobacz PKCE RFC.

Tło OAuth 2.0 dla aplikacji systemu Windows

Windows Runtime (WinRT) WebAuthenticationBroker, przeznaczony głównie dla aplikacji platformy UWP, stanowi pewne wyzwania podczas użycia w aplikacjach desktopowych. Kluczowe problemy obejmują zależność od ApplicationView, która nie jest zgodna z frameworkami do aplikacji desktopowych. W rezultacie deweloperzy muszą uciekać się do obejść z użyciem interfejsów międzyoperacyjności i dodatkowego kodu w celu zaimplementowania funkcji OAuth 2.0 w WinUI i innych aplikacjach desktopowych.

Interfejs API OAuth2Manager w Windows App SDK

Interfejs API OAuth2Manager dla Windows App SDK zapewnia usprawnione rozwiązanie spełniające oczekiwania deweloperów. Oferuje ona bezproblemowe funkcje protokołu OAuth 2.0 z pełną parzystością funkcji na wszystkich platformach systemu Windows obsługiwanych przez Windows App SDK. Nowy interfejs API eliminuje potrzebę kłopotliwych obejść i upraszcza proces dołączania funkcji OAuth 2.0 do aplikacji klasycznych.

Element OAuth2Manager różni się od elementu WebAuthenticationBroker w środowisku WinRT. Jest to bardziej zgodne z najlepszymi rozwiązaniami protokołu OAuth 2.0 — na przykład przy użyciu domyślnej przeglądarki użytkownika. Najlepsze rozwiązania dotyczące interfejsu API pochodzą z IETF (Internet Engineering Task Force) OAuth 2.0 Authorization Framework RFC 6749, PKCE RFC 7636 i OAuth 2.0 for Native Apps RFC 8252.

Przykłady kodu OAuth 2.0

Pełna przykładowa aplikacja WinUI jest dostępna w GitHub. Poniższe sekcje zawierają fragmenty kodu dla najbardziej typowych przepływów protokołu OAuth 2.0 przy użyciu interfejsu API OAuth2Manager.

Żądanie kodu autoryzacji

W poniższym przykładzie pokazano, jak wykonać żądanie kodu autoryzacji przy użyciu OAuth2Manager w 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());
}

Wymień kod autoryzacji na token dostępu

W poniższym przykładzie pokazano, jak wymienić kod autoryzacji na token dostępu przy użyciu OAuth2Manager.

W przypadku klientów publicznych, takich jak natywne aplikacje klasyczne, korzystających z PKCE, nie dołączaj tajnego hasła klienta. Zamiast tego, weryfikator kodu PKCE zapewnia zabezpieczenia.

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

W przypadku klientów poufnych (takich jak aplikacje internetowe lub usługi), które mają tajny klucz klienta, dołącz 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

Odśwież token dostępu

W poniższym przykładzie pokazano, jak odświeżyć token dostępu przy użyciu metody OAuth2Manager's RefreshTokenAsync.

W przypadku klientów publicznych korzystających z PKCE pomiń 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());
}

W przypadku poufnych klientów, którzy mają tajny klucz klienta, dołącz parametr 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

Ukończ żądanie autoryzacji

Aby ukończyć żądanie autoryzacji z aktywacji protokołu, aplikacja powinna obsługiwać zdarzenie AppInstance.Activated . To zdarzenie jest wymagane, gdy aplikacja ma niestandardową logikę przekierowania. Pełny przykład jest dostępny w GitHub.

Użyj następującego kodu:

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

        DisplayUnhandledMessageToUser();
    }
}