web-dev-qa-db-fra.com

Configurer le point de terminaison du serveur d'autorisation

Question

Comment utilisons-nous un jeton porteur avec ASP.NET 5 en utilisant un flux de nom d'utilisateur et de mot de passe? Pour notre scénario, nous voulons permettre à un utilisateur de s'inscrire et de se connecter en utilisant AJAX appels sans avoir besoin d'utiliser une connexion externe.

Pour ce faire, nous devons disposer d'un point de terminaison de serveur d'autorisation. Dans les versions précédentes d'ASP.NET , nous procédions comme suit, puis nous nous connections à l'URL ourdomain.com/Token.

// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AccessTokenExpireTimeSpan = TimeSpan.FromDays(14)
};

Dans la version actuelle d'ASP.NET, cependant, ce qui précède ne fonctionne pas. Nous avons essayé de comprendre la nouvelle approche. exemple aspnet/identité sur GitHub, par exemple, configure l'authentification Facebook, Google et Twitter mais ne semble pas configurer un point de terminaison de serveur d'autorisation non externe OAuth autorisation, sauf si c'est ce que fait AddDefaultTokenProviders() , auquel cas nous nous demandons quelle serait l'URL du fournisseur.

Recherche

Nous avons appris de en lisant la source ici que nous pouvons ajouter "middleware d'authentification au porteur" au pipeline HTTP en appelant IAppBuilder.UseOAuthBearerAuthentication Dans notre classe Startup. C'est un bon début même si nous ne savons toujours pas comment définir son point de terminaison de jeton. Cela n'a pas fonctionné:

public void Configure(IApplicationBuilder app)
{  
    app.UseOAuthBearerAuthentication(options =>
    {
        options.MetadataAddress = "meta";
    });

    // if this isn't here, we just get a 404
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello World.");
    });
}

En allant sur ourdomain.com/meta Nous venons de recevoir notre page Hello World.

Des recherches plus poussées ont montré que nous pouvons également utiliser la méthode d'extension IAppBuilder.UseOAuthAuthentication Et qu'elle prend un paramètre OAuthAuthenticationOptions. Ce paramètre a une propriété TokenEndpoint . Donc, bien que nous ne soyons pas sûrs de ce que nous faisons, nous avons essayé cela, ce qui bien sûr n'a pas fonctionné.

public void Configure(IApplicationBuilder app)
{
    app.UseOAuthAuthentication("What is this?", options =>
    {
        options.TokenEndpoint = "/token";
        options.AuthorizationEndpoint = "/oauth";
        options.ClientId = "What is this?";
        options.ClientSecret = "What is this?";
        options.SignInScheme = "What is this?";
        options.AutomaticAuthentication = true;
    });

    // if this isn't here, we just get a 404
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello World.");
    });
}

En d'autres termes, en allant à ourdomain.com/token, Il n'y a pas d'erreur, il y a juste à nouveau notre page Hello World.

38
Shaun Luttin

Bon, récapitulons les différents middlewares OAuth2 (et leurs extensions IAppBuilder respectives) qui étaient proposés par OWIN/Katana 3 et ceux qui sera porté sur ASP.NET Core :

  • app.UseOAuthBearerAuthentication/OAuthBearerAuthenticationMiddleware: son nom n'était pas très évident, mais il était (et est toujours, car il a été porté sur ASP.NET Core) responsable de la validation des jetons d'accès émis par le middleware du serveur OAuth2. Il s'agit essentiellement de la contrepartie de jeton du middleware de cookies et est utilisé pour protéger vos API. Dans ASP.NET Core, il a été enrichi de fonctionnalités OpenID Connect en option (il est désormais capable de récupérer automatiquement le certificat de signature du serveur OpenID Connect qui émis les jetons).

Remarque: à partir d'ASP.NET Core beta8, il s'appelle désormaisapp.UseJwtBearerAuthentication/JwtBearerAuthenticationMiddleware.

  • app.UseOAuthAuthorizationServer/OAuthAuthorizationServerMiddleware: comme son nom l'indique, OAuthAuthorizationServerMiddleware était un middleware de serveur d'autorisation OAuth2 et était utilisé pour créer et émettre des jetons d'accès. Ce middleware ne sera pas porté sur ASP.NET Core : Service d'autorisation OAuth dans ASP.NET Core .

  • app.UseOAuthBearerTokens: cette extension ne correspondait pas vraiment à un middleware et était simplement un wrapper autour de app.UseOAuthAuthorizationServer et app.UseOAuthBearerAuthentication. Il faisait partie du package d'identité ASP.NET et n'était qu'un moyen pratique de configurer à la fois le serveur d'autorisation OAuth2 et le middleware porteur OAuth2 utilisé pour valider les jetons d'accès en un seul appel. Il ne sera pas porté sur ASP.NET Core .

ASP.NET Core offrira un tout nouveau middleware (et je suis fier de dire que je l'ai conçu):

  • app.UseOAuthAuthentication/OAuthAuthenticationMiddleware: ce nouveau middleware est un client interactif générique OAuth2 qui se comporte exactement comme app.UseFacebookAuthentication ou app.UseGoogleAuthentication mais qui prend en charge pratiquement tous les fournisseurs OAuth2 standard, y compris le vôtre. Les fournisseurs Google, Facebook et Microsoft ont tous été mis à jour pour hériter de ce nouveau middleware de base.

Ainsi, le middleware que vous recherchez réellement est le middleware du serveur d'autorisation OAuth2 , alias OAuthAuthorizationServerMiddleware.

Bien qu'il soit considéré comme un composant essentiel par une grande partie de la communauté, il ne sera pas porté sur ASP.NET Core .

Heureusement, il existe déjà un remplacement direct: AspNet.Security.OpenIdConnect.Server ( https://github.com/aspnet-contrib/ AspNet.Security.OpenIdConnect.Server )

Ce middleware est un fork avancé du middleware du serveur d'autorisation OAuth2 fourni avec Katana 3 mais qui cible OpenID Connect (qui est lui-même basé sur OAuth2). Il utilise la même approche de bas niveau qui offre un contrôle précis (via diverses notifications) et vous permet d'utiliser votre propre framework (Nancy, ASP.NET Core MVC) pour servir vos pages d'autorisation comme vous le pourriez avec le middleware du serveur OAuth2 . La configuration est simple:

ASP.NET Core 1.x:

// Add a new middleware validating access tokens issued by the server.
app.UseOAuthValidation();

// Add a new middleware issuing tokens.
app.UseOpenIdConnectServer(options =>
{
    options.TokenEndpointPath = "/connect/token";

    // Create your own `OpenIdConnectServerProvider` and override
    // ValidateTokenRequest/HandleTokenRequest to support the resource
    // owner password flow exactly like you did with the OAuth2 middleware.
    options.Provider = new AuthorizationProvider();
});

ASP.NET Core 2.x:

// Add a new middleware validating access tokens issued by the server.
services.AddAuthentication()
    .AddOAuthValidation()

    // Add a new middleware issuing tokens.
    .AddOpenIdConnectServer(options =>
    {
        options.TokenEndpointPath = "/connect/token";

        // Create your own `OpenIdConnectServerProvider` and override
        // ValidateTokenRequest/HandleTokenRequest to support the resource
        // owner password flow exactly like you did with the OAuth2 middleware.
        options.Provider = new AuthorizationProvider();
    });

Il existe une version OWIN/Katana 3 et une ASP.NET Core version qui prend en charge .NET Desktop et .NET Core.

N'hésitez pas à donner l'exemple Postman un essai pour comprendre comment cela fonctionne. Je vous recommande de lire le billet de blog associé , qui explique comment vous pouvez implémenter le flux de mots de passe du propriétaire de la ressource.

N'hésitez pas à me cingler si vous avez encore besoin d'aide. Bonne chance!

47
Pinpoint

Avec l'aide de @ Pinpoint, nous avons câblé ensemble les rudiments d'une réponse. Il montre comment les composants sont connectés sans être une solution complète.

Démo Fiddler

Avec notre configuration de projet rudimentaire, nous avons pu faire la demande et la réponse suivantes dans Fiddler.

Demande

POST http://localhost:50000/connect/token HTTP/1.1
User-Agent: Fiddler
Host: localhost:50000
Content-Length: 61
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=my_username&password=my_password

Réponse

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 1687
Content-Type: application/json;charset=UTF-8
Expires: -1
X-Powered-By: ASP.NET
Date: Tue, 16 Jun 2015 01:24:42 GMT

{
  "access_token" : "eyJ0eXAiOi ... 5UVACg",
  "expires_in" : 3600,
  "token_type" : "bearer"
}

La réponse fournit un jeton porteur que nous pouvons utiliser pour accéder à la partie sécurisée de l'application.

Structure du projet

C'est la structure de notre projet dans Visual Studio. Nous avons dû définir son Properties> Debug> Port sur 50000 Afin qu'il agisse comme le serveur d'identité que nous avons configuré. Voici les fichiers pertinents:

ResourceOwnerPasswordFlow
    Providers
        AuthorizationProvider.cs
    project.json
    Startup.cs

Startup.cs

Pour plus de lisibilité, j'ai divisé la classe Startup en deux partiels.

Startup.ConfigureServices

Pour les bases, nous avons seulement besoin de AddAuthentication().

public partial class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication();
    }
}

Démarrage.Configurez

public partial class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();

        // Add a new middleware validating access tokens issued by the server.
        app.UseJwtBearerAuthentication(new JwtBearerOptions
        {
            AutomaticAuthenticate = true,
            AutomaticChallenge = true,
            Audience = "resource_server_1",
            Authority = "http://localhost:50000/",
            RequireHttpsMetadata = false
        });

        // Add a new middleware issuing tokens.
        app.UseOpenIdConnectServer(options =>
        {
            // Disable the HTTPS requirement.
            options.AllowInsecureHttp = true;

            // Enable the token endpoint.
            options.TokenEndpointPath = "/connect/token";

            options.Provider = new AuthorizationProvider();

            // Force the OpenID Connect server middleware to use JWT
            // instead of the default opaque/encrypted format.
            options.AccessTokenHandler = new JwtSecurityTokenHandler
            {
                InboundClaimTypeMap = new Dictionary<string, string>(),
                OutboundClaimTypeMap = new Dictionary<string, string>()
            };

            // Register an ephemeral signing key, used to protect the JWT tokens.
            // On production, you'd likely prefer using a signing certificate.
            options.SigningCredentials.AddEphemeralKey();
        });

        app.UseMvc();

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}

AuthorizationProvider.cs

public sealed class AuthorizationProvider : OpenIdConnectServerProvider
{
    public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
    {
        // Reject the token requests that don't use
        // grant_type=password or grant_type=refresh_token.
        if (!context.Request.IsPasswordGrantType() &&
            !context.Request.IsRefreshTokenGrantType())
        {
            context.Reject(
                error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
                description: "Only grant_type=password and refresh_token " +
                             "requests are accepted by this server.");

            return Task.FromResult(0);
        }

        // Since there's only one application and since it's a public client
        // (i.e a client that cannot keep its credentials private), call Skip()
        // to inform the server that the request should be accepted without 
        // enforcing client authentication.
        context.Skip();

        return Task.FromResult(0);
    }

    public override Task HandleTokenRequest(HandleTokenRequestContext context)
    {
        // Only handle grant_type=password token requests and let the
        // OpenID Connect server middleware handle the other grant types.
        if (context.Request.IsPasswordGrantType())
        {
            // Validate the credentials here (e.g using ASP.NET Core Identity).
            // You can call Reject() with an error code/description to reject
            // the request and return a message to the caller.

            var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
            identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "[unique identifier]");

            // By default, claims are not serialized in the access and identity tokens.
            // Use the overload taking a "destinations" parameter to make sure 
            // your claims are correctly serialized in the appropriate tokens.
            identity.AddClaim("urn:customclaim", "value",
                OpenIdConnectConstants.Destinations.AccessToken,
                OpenIdConnectConstants.Destinations.IdentityToken);

            var ticket = new AuthenticationTicket(
                new ClaimsPrincipal(identity),
                new AuthenticationProperties(),
                context.Options.AuthenticationScheme);

            // Call SetResources with the list of resource servers
            // the access token should be issued for.
            ticket.SetResources("resource_server_1");

            // Call SetScopes with the list of scopes you want to grant
            // (specify offline_access to issue a refresh token).
            ticket.SetScopes("profile", "offline_access");

            context.Validate(ticket);
        }

        return Task.FromResult(0);
    }
}

project.json

{
  "dependencies": {
    "AspNet.Security.OpenIdConnect.Server": "1.0.0",
    "Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.0",
  }

  // other code omitted
}
31
Shaun Luttin