Udostępnij przez


Implementowanie protokołu OAuth 2.0 w aplikacjach systemu Windows

OAuth2Manager w zestawie 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 przyznawania kodu autoryzacyjnego z kluczem uwierzytelniania dla wymiany kodu (PKCE). Aby uzyskać więcej informacji, zobacz PKCE RFC.

Tło OAuth 2.0 dla aplikacji systemu Windows

Środowisko uruchomieniowe systemu Windows (WinRT) WebAuthenticationBroker, przeznaczone przede wszystkim dla aplikacji platformy UWP, stwarza kilka wyzwań w przypadku 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ść dotyczących interfejsów międzyoperacyjności i dodatkowego kodu w celu zaimplementowania funkcji OAuth 2.0 w aplikacjach wykorzystujących WinUI 3 oraz innych aplikacjach desktopowych.

Interfejs API OAuth2Manager w zestawie SDK aplikacji systemu Windows

Interfejs API OAuth2Manager dla zestawu SDK aplikacji systemu Windows udostępnia 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 zestaw SDK aplikacji systemu Windows. 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 3 jest dostępna w witrynie 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 zestawie SDK aplikacji systemu Windows:

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

Kod autoryzacji programu Exchange dla tokenu dostępu

W poniższym przykładzie pokazano, jak wymienić kod autoryzacji dla tokenu dostępu przy użyciu protokołu 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 poufnych klientów (takich jak aplikacje internetowe lub usługi), które mają klucz tajny klienta, dołącz ClientAuthentication parametr:

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żanie tokenu dostępu

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

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