Using Claims with OpenIdConnect.Server in ASP.NET 5
Asked Answered
G

1

4

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)?
Galimatias answered 30/8, 2015 at 17:10 Comment(1)
The InvalidOperationException is caused by the fact there's no server at http://win2012.bludev.com/api/auth/. When the OAuth2 bearer middleware tries to download the provider's configuration metadata, it crashes and returns an exception.Montespan
H
2

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

Hitherto answered 30/8, 2015 at 18:28 Comment(1)
Thanks it worked like a charm. Before I used identity.AddClaim(new Claim(ClaimTypes.Name, username));. Assuming I'd have a higher number of claims (organized by groups like Users and actions Create, Update, AssignRoles etc) and don't want to put them all in the jwt token (for both token size and confidentiality) but still need them server sided for authorizatoin, would I have to create a Middleware that fetches them from the server (or Redis cache)? Or what's the way to goGalimatias

© 2022 - 2024 — McMap. All rights reserved.