Using Claims with OpenIdConnect.Server in ASP.NET

2019-05-04 02:05发布

问题:

In the past 7 days I've tried to setup an ASP.NET 5 WebApi using OpenIdConnect.Server with the resource owner flow.

I was more or less successful in generating a token and accessing [Authorize] protected actions.

However, when I try to access this.User.Identity.Claims, it's empty. I am using ASP.NET 5, beta6 for now (having troubles upgrading to most recent beta7 and waiting for it's official release)

In the Startup.cs I got the following:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCaching();

    services.AddEntityFramework()
        .AddSqlServer()
        .AddDbContext<AuthContext>(options =>
        {
            options.UseSqlServer(Configuration.Get("Data:DefaultConnection:ConnectionString"));
        });

    services.AddIdentity<AuthUser, AuthRole>(
        options => options.User = new Microsoft.AspNet.Identity.UserOptions
        {
            RequireUniqueEmail = true,
            UserNameValidationRegex = "^[a-zA-Z0-9@_\\.-]+$"
        })
        .AddEntityFrameworkStores<AuthContext, Guid>()
        .AddDefaultTokenProviders();

    services.ConfigureCors(configure =>
    {
        configure.AddPolicy("CorsPolicy", builder =>
        {
            builder.WithOrigins("http:/localhost/", "http://win2012.bludev.com/");
        });
    });

    services.AddScoped<IAuthRepository, AuthRepository>();
}

    public void Configure(IApplicationBuilder app)
    {
        var factory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
        factory.AddConsole();

        app.UseStaticFiles();

        app.UseOAuthBearerAuthentication(options =>
        {
            options.Authority = "http://win2012.bludev.com/api/auth/";
            options.Audience = "http://win2012.bludev.com/";

            options.AutomaticAuthentication = true;

            options.TokenValidationParameters = new TokenValidationParameters()
            {
                RequireExpirationTime = true,
                RequireSignedTokens = true,
                RoleClaimType = ClaimTypes.Role,
                NameClaimType = ClaimTypes.NameIdentifier,

                ValidateActor = true,
                ValidateAudience = false,
                ValidateIssuer = true,
                ValidateLifetime = false,
                ValidateIssuerSigningKey = true,
                ValidateSignature = true,

                ValidAudience = "http://win2012.bludev.com/",
                ValidIssuer = "http://win2012.bludev.com/"
            };
        });

        app.UseOpenIdConnectServer(options =>
        {
            options.Issuer = new Uri("http://win2012.bludev.com/api/auth/");
            options.AllowInsecureHttp = true;
            options.AuthorizationEndpointPath = PathString.Empty;
            options.Provider = new AuthorizationProvider();
            options.ApplicationCanDisplayErrors = true;

            // Note: in a real world app, you'd probably prefer storing the X.509 certificate
            // in the user or machine store. To keep this sample easy to use, the certificate
            // is extracted from the Certificate.pfx file embedded in this assembly.
            options.UseCertificate(
                assembly: typeof(Startup).GetTypeInfo().Assembly,
                resource: "AuthExample.Certificate.pfx",
                password: "Owin.Security.OpenIdConnect.Server");
        });

        app.UseIdentity();

        app.UseMvc();
    }
}

I used app.UseOAuthBearerAuthentication because I couldn't get app.UseOpenIdConnectAuthentication working, all I would get is this in the console:

request: /admin/user/ warning : [Microsoft.AspNet.Authentication.OpenIdConnect.OpenIdConnectAuthentica tionMiddleware] OIDCH_0004: OpenIdConnectAuthenticationHandler: message.State is null or empty. request: /.well-known/openid-configuration warning : [Microsoft.AspNet.Authentication.OpenIdConnect.OpenIdConnectAuthentica tionMiddleware] OIDCH_0004: OpenIdConnectAuthenticationHandler: message.State is null or empty.

and an Exception after the time out

error : [Microsoft.AspNet.Server.WebListener.MessagePump] ProcessRequestAsync System.InvalidOperationException: IDX10803: Unable to create to obtain configura tion from: 'http://win2012.bludev.com/api/auth/.well-known/openid-configuration' . at Microsoft.IdentityModel.Logging.LogHelper.Throw(String message, Type excep tionType, EventLevel logLevel, Exception innerException) at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.d__24.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot ification(Task task) ...

With this configuration UseOpenIdConnectAuthentication

app.UseOpenIdConnectAuthentication(options =>
{
    options.AuthenticationScheme = OpenIdConnectAuthenticationDefaults.AuthenticationScheme;

    options.Authority = "http://win2012.bludev.com/api/auth/";
    options.Audience = "http://win2012.bludev.com/";
    options.Resource = "http://win2012.bludev.com/";

    options.AutomaticAuthentication = true;

    options.TokenValidationParameters = new TokenValidationParameters()
    {
        RequireExpirationTime = true,
        RequireSignedTokens = true,
        RoleClaimType = ClaimTypes.Role,
        NameClaimType = ClaimTypes.NameIdentifier,

        ValidateActor = true,
        ValidateAudience = false,
        ValidateIssuer = true,
        ValidateLifetime = false,
        ValidateIssuerSigningKey = true,
        ValidateSignature = true
    };

});

So the real question is:

  1. How to get resource owner flow to work with claims
  2. ValidateLifetime = true or ValidateAudience = true would throw exception and result in a Http Code 500 response without a printed error.
  3. How to turn authentication failures into a meaningful 400/403 code and a json or xml respones (depending on the client preference) to be displayed for the user? (JavaScript is the client in this case)?

回答1:

app.UseOpenIdConnectAuthentication() (which relies on OpenIdConnectAuthenticationMiddleware) is only meant to support interactive flows (code/implicit/hybrid) and cannot be used with the resource owner password credentials grant type. Since you only want to validate access tokens, use app.UseOAuthBearerAuthentication() instead.

See this SO answer for more information about the different OpenID Connect/OAuth2 middleware in ASP.NET 5: Configure the authorization server endpoint

How to get resource owner flow to work with claims

The entire OpenIdConnectServerMiddleware you're using is based on claims.

If you have trouble serializing specific claims, remember that all claims except ClaimTypes.NameIdentifier are not serialized by default in the identity and access tokens, since they are both readable by the client application and the user agent. To avoid leaking confidential data, you need to specify an explicit destination indicating where you want the claims to be serialized:

// This claim will be only serialized in the access token.
identity.AddClaim(ClaimTypes.Name, username, OpenIdConnectConstants.Destinations.AccessToken);

// This claim will be serialized in both the identity and the access tokens.
identity.AddClaim(ClaimTypes.Surname, "Doe",
    OpenIdConnectConstants.Destinations.AccessToken,
    OpenIdConnectConstants.Destinations.IdentityToken););

ValidateLifetime = true or ValidateAudience = true would throw exception and result in a Http Code 500 response without a printed error.

How to turn authentication failures into a meaningful 400/403 code and a json or xml respones (depending on the client preference) to be displayed for the user? (JavaScript is the client in this case)?

That's how the OIDC client middleware (managed by MSFT) currently works by default, but it will be eventually fixed. You can see this GitHub ticket a workaround: https://github.com/aspnet/Security/issues/411