Compartir a través de


Implementación de OAuth 2.0 en aplicaciones de Windows

El OAuth2Manager en Windows App SDK permite a las aplicaciones de escritorio, tales como WinUI 3, llevar a cabo sin problemas la autorización de OAuth 2.0 en Windows. La API de OAuth2Manager no proporciona API para la solicitud implícita y la credencial de contraseña del propietario del recurso debido a los problemas de seguridad que conlleva. Utilice el tipo de concesión de código de autorización con Código de Prueba para el Intercambio de Código (PKCE). Para obtener más información, consulte PKCE RFC.

Fondo de OAuth 2.0 para aplicaciones de Windows

Windows Runtime (WinRT) WebAuthenticationBroker, diseñado principalmente para aplicaciones para UWP, presenta varios desafíos cuando se usa en aplicaciones de escritorio. Entre los problemas clave se incluyen la dependencia de ApplicationView, que no es compatible con marcos de aplicaciones de escritorio. Como resultado, los desarrolladores deben recurrir a soluciones alternativas que implican interfaces de interoperabilidad y código adicional para implementar la funcionalidad de OAuth 2.0 en WinUI 3 y otras aplicaciones de escritorio.

API de OAuth2Manager en Windows App SDK

La API de OAuth2Manager para Windows App SDK proporciona una solución simplificada que satisface las expectativas de los desarrolladores. Ofrece funcionalidades de OAuth 2.0 sin problemas con paridad de características completa en todas las plataformas de Windows compatibles con Windows App SDK. La nueva API elimina la necesidad de soluciones complicadas y simplifica el proceso de incorporación de la funcionalidad de OAuth 2.0 en aplicaciones de escritorio.

OAuth2Manager es diferente de WebAuthenticationBroker en WinRT. Sigue los procedimientos recomendados de OAuth 2.0 más detenidamente, por ejemplo, mediante el explorador predeterminado del usuario. Los procedimientos recomendados para la API proceden de IETF (Internet Engineering Task Force) OAuth 2.0 Authorization Framework RFC 6749, PKCE RFC 7636 y OAuth 2.0 for Native Apps RFC 8252.

Ejemplos de código de OAuth 2.0

Hay disponible una aplicación de ejemplo completa de WinUI 3 en GitHub. En las secciones siguientes se proporcionan fragmentos de código para los flujos de OAuth 2.0 más comunes mediante la API de de OAuth2Manager.

Solicitud de código de autorización

En el ejemplo siguiente se muestra cómo realizar una solicitud de código de autorización mediante el OAuth2Manager en 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());
}

Código de autorización de Exchange para el token de acceso

En el ejemplo siguiente se muestra cómo intercambiar un código de autorización para un token de acceso mediante OAuth2Manager.

En el caso de los clientes públicos (como las aplicaciones de escritorio nativas) que usan PKCE, no incluyan un secreto de cliente. El comprobador de código PKCE proporciona la seguridad en su lugar:

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

Para clientes confidenciales (como aplicaciones web o servicios) que tienen un secreto de cliente, incluya el ClientAuthentication parámetro :

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

Actualización de un token de acceso

En el ejemplo siguiente se muestra cómo actualizar un token de acceso mediante el método RefreshTokenAsync de OAuth2Manager.

En el caso de los clientes públicos que usan PKCE, omita el ClientAuthentication parámetro :

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

Para los clientes confidenciales que tienen un secreto de cliente, incluya el ClientAuthentication parámetro :

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

Completar una solicitud de autorización

Para completar una solicitud de autorización desde una activación de protocolo, la aplicación debe controlar el evento AppInstance.Activated . Este evento es necesario cuando la aplicación tiene lógica de redirección personalizada. Hay disponible un ejemplo completo en gitHub.

Use el código siguiente:

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

        DisplayUnhandledMessageToUser();
    }
}