Validación de tokens emitidos por AspNet.Security.OpenIdConnect.Server (ASP.NET vNext)

Estoy usando Visual Studio 2015 Enterprise y ASP.NET vNext Beta8 para crear un punto final que emita y consuma tokens JWT. Originalmente abordé esto generando los tokens yo mismo, como se describeaquí. Más tarde un útilartículo by @Pinpoint reveló que AspNet.Security.OpenIdConnect.Server (a.k.a. OIDC) se puede configurar para emitir y consumir los tokens por mí.

Así que seguí esas instrucciones, puse un punto final y envié una publicación x-www-form-urlencoded decartero Recibo un token legítimo:

{
  "token_type": "bearer",
  "access_token": "eyJ0eXAiO....",
  "expires_in": "3599"
}

Esto es genial, pero también donde me quedo atascado. Ahora, ¿cómo anoto una acción del controlador para que exija este token de portador?

Pensé que todo lo que tendría que hacer es decorar mi método de controlador con [Autorizar ("Portador")], agregar un esquema de autenticación:

        services.AddAuthorization
        (
            options => 
            {
                options.AddPolicy
                (
                    JwtBearerDefaults.AuthenticationScheme, 
                    builder => 
                    {
                        builder.
                        AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme).
                        RequireAuthenticatedUser().
                        Build();
                    } 
                );
            }
        );

Y luego llame a mi acción de controlador con el encabezado "Portador de autorización eyJ0eXAiO ...." como lo había hecho en mi ejemplo anterior. Lamentablemente, todo lo que parece hacer este enfoque es generar una excepción:

Se produjo una excepción no controlada al procesar la solicitud.

SocketException: no se pudo establecer conexión porque la máquina de destino la rechazó activamente 127.0.0.1:50000

WebException: no se puede conectar al servidor remoto

HttpRequestException: se produjo un error al enviar la solicitud.

IOException: IDX10804: no se puede recuperar el documento de: 'http: // localhost: 50000 / .well-known / openid-configuration'. Microsoft.IdentityModel.Logging.LogHelper.Throw (Mensaje de cadena, Tipo exceptionType, EventLevel logLevel, Exception innerException)

InvalidOperationException: IDX10803: no se puede obtener la configuración de: 'http: // localhost: 50000 / .well-known / openid-configuration'. Excepción interna: 'IDX10804: no se puede recuperar el documento de:'http: // localhost: 50000 / .well-known / openid-configuration'.'.


Considere los siguientes pasos para reproducir (pero no considere este código digno de producción):

Aplique las herramientas ASP.NET Beta8 como se describeaquí

Abra Visual Studio Enterprise 2015 y cree un nuevo proyecto de plantilla de vista previa de API web ASP.NET 5

Cambiar project.json

{
"webroot": "wwwroot",
"versión": "1.0.0- *",

"dependencias": {
"Microsoft.AspNet.IISPlatformHandler": "1.0.0-beta8",
"Microsoft.AspNet.Mvc": "6.0.0-beta8",
"Microsoft.AspNet.Server.Kestrel": "1.0.0-beta8",
"Microsoft.AspNet.Authentication.JwtBearer": "1.0.0-beta8",
"AspNet.Security.OpenIdConnect.Server": "1.0.0-beta3",
"Microsoft.AspNet.Authentication.OpenIdConnect": "1.0.0-beta8",
"Microsoft.Framework.ConfigurationModel.Json": "1.0.0-beta4",
"Microsoft.AspNet.Diagnostics": "1.0.0-beta8"
},

"comandos": {
"web": "Microsoft.AspNet.Server.Kestrel"
},

"marcos": {
"dnx451": {}
},

"excluir": [
"wwwroot",
"node_modules"
],
"publishingExclude": [
".usuario",
"
.vspscc "
]
}

Cambie Startup.cs de la siguiente manera (esto es cortesía del artículo original de @ Pinpoint; eliminé los comentarios y agregué el fragmento AddAuthorization):

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthorization
        (
            options => 
            {
                options.AddPolicy
                (
                    JwtBearerDefaults.AuthenticationScheme, 
                    builder => 
                    {
                        builder.
                        AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme).
                        RequireAuthenticatedUser().
                        Build();
                    } 
                );
            }
        );
        services.AddAuthentication();
        services.AddCaching();
        services.AddMvc();
        services.AddOptions();
    }

    // Configure is called after ConfigureServices is called.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions<AppSettings> appSettings)
    {
        app.UseDeveloperExceptionPage();

        // Add a new middleware validating access tokens issued by the OIDC server.
        app.UseJwtBearerAuthentication(options => {
            options.AutomaticAuthentication = true;
            options.Audience = "http://localhost:50000/";
            options.Authority = "http://localhost:50000/";
            options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>
            (
                metadataAddress : options.Authority + ".well-known/openid-configuration",
                configRetriever : new OpenIdConnectConfigurationRetriever(),
                docRetriever    : new HttpDocumentRetriever { RequireHttps = false }
            );
        });

        // Add a new middleware issuing tokens.
        app.UseOpenIdConnectServer
        (
            configuration => 
            {
                configuration.Options.TokenEndpointPath= "/authorization/v1";
                configuration.Options.AllowInsecureHttp = true;
                configuration.Provider = new OpenIdConnectServerProvider {

                    OnValidateClientAuthentication = context => 
                    {
                        context.Skipped();
                        return Task.FromResult<object>(null);
                    },

                    OnGrantResourceOwnerCredentials = context => 
                    {
                        var identity = new ClaimsIdentity(OpenIdConnectDefaults.AuthenticationScheme);
                        identity.AddClaim( new Claim(ClaimTypes.NameIdentifier, "todo")  );
                        identity.AddClaim( new Claim("urn:customclaim", "value", "token id_token"));
                        context.Validated(new ClaimsPrincipal(identity));
                        return Task.FromResult<object>(null);
                    }
                };
            }
        );

        app.UseMvc();
    }
}
Cambie ValuesController.cs extraño para especificar un atributo Autorizar:
[Route("api/[controller]")]
public class ValuesController : Controller
{
    // GET: api/values
    [Authorize("Bearer")] 
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }
}

Ejecute el proyecto y adquiera un token utilizandocartero. Para adquirir un token, use POST x-www-form-urlencoded con "grant_type" de "contraseña", "nombre de usuario" cualquier cosa, "contraseña" cualquier cosa y "recurso" la dirección del punto final API. Mi URL particular, por ejemplo, eshttp: // localhost: 37734 / autorización / v1.

Copie el token codificado Base64, luego use el token para llamar al controlador de valores extraños usandocartero. Para usar el token, haga un GET con los encabezados Content-Type application / json y Authorization bearer eyJ0eXAiO .... (su token). Mi URL particular eshttp: // localhost: 37734 / api / values.

Observe la excepción mencionada anteriormente.

Si el enfoque [Autorizar ("Portador") que estoy probando anteriormente es el camino equivocado, agradecería que alguien me ayudara a comprender las mejores prácticas sobre cómo ingerir el token JWT usando OIDC.

Gracias.

Respuestas a la pregunta(1)

Su respuesta a la pregunta