3

I have followed everything I know from posts regarding how to implement AspNet.Security.OpenIdConnect.Server.

Pinpoint, do you hear me? ;)

I've managed to separate token issuing and token consumption. I won't show the "auth server side" because I think that part is all set, but I'll show how I built the authentication ticket inside my custom AuthorizationProvider:

public sealed class AuthorizationProvider : OpenIdConnectServerProvider
{
    // The other overrides are not show. I've relaxed them to always validate.

    public override async Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context)
    {
          // I'm using Microsoft.AspNet.Identity to validate user/password. 
          // So, let's say that I already have MyUser user from
          //UserManager<MyUser> UM:

            var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
            //identity.AddClaims(await UM.GetClaimsAsync(user));
            identity.AddClaim(ClaimTypes.Name, user.UserName);

            (await UM.GetRolesAsync(user)).ToList().ForEach(role => {
                identity.AddClaim(ClaimTypes.Role, role);
            });

            var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity),
                                                  new AuthenticationProperties(),
                                                  context.Options.AuthenticationScheme);
            // Some new stuff, per my latest research
            ticket.SetResources(new[] { "my_resource_server" });
            ticket.SetAudiences(new[] { "my_resource_server" });
            ticket.SetScopes(new[] { "defaultscope" });

            context.Validated(ticket);
        }
    }

And startup at the auth server:

using System;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Data.Entity;
using Microsoft.Extensions.DependencyInjection;

using MyAuthServer.Providers;

namespace My.AuthServer
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication();
            services.AddCaching();
            services.AddMvc();

            string connectionString = "there is actually one";

            services.AddEntityFramework()
                    .AddSqlServer()
                    .AddDbContext<MyDbContext>(options => {
                        options.UseSqlServer(connectionString).UseRowNumberForPaging();
                    });

            services.AddIdentity<User, Role>()
                    .AddEntityFrameworkStores<MyDbContext>().AddDefaultTokenProviders();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseIISPlatformHandler();

            app.UseOpenIdConnectServer(options => {
                options.ApplicationCanDisplayErrors = true;
                options.AllowInsecureHttp = true;                
                options.Provider = new AuthorizationProvider();
                options.TokenEndpointPath = "/token";
                options.AccessTokenLifetime = new TimeSpan(1, 0, 0, 0);
                options.Issuer = new Uri("http://localhost:60556/");
            });

            app.UseMvc();
            app.UseWelcomePage();
        }

        public static void Main(string[] args) => WebApplication.Run<Startup>(args);
    }
}

Sure enough, when I have this HTTP request, I do get an access token, but I'm not sure if that access token has all the data that the resource server expects.

POST /token HTTP/1.1
Host: localhost:60556
Content-Type: application/x-www-form-urlencoded

username=admin&password=pw&grant_type=password

Now, At the resource server side, I'm using JWT Bearer Authentication. On startup, I've got:

using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Data.Entity;
using Microsoft.Extensions.DependencyInjection;

namespace MyResourceServer
{
    public class Startup
    {            
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            string connectionString = "there is actually one";

            services.AddEntityFramework()
                    .AddSqlServer()
                    .AddDbContext<MyDbContext>(options => {
                        options.UseSqlServer(connectionString).UseRowNumberForPaging();                        
                    });

            services.AddIdentity<User, Role>()
                    .AddEntityFrameworkStores<MyDbContext>().AddDefaultTokenProviders();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseIISPlatformHandler();
            app.UseMvc();
            app.UseWelcomePage();

            app.UseJwtBearerAuthentication(options => {
                options.Audience = "my_resource_server";  
                options.Authority = "http://localhost:60556/"; 
                options.AutomaticAuthenticate = true;
                options.RequireHttpsMetadata = false;                
            });
        }

        public static void Main(string[] args) => WebApplication.Run<Startup>(args);
    }
}

When I make this HTTP request to the resource server, I get a 401 Unauthorized:

GET /api/user/myroles HTTP/1.1
Host: localhost:64539
Authorization: Bearer eyJhbGciOiJS...
Content-Type: application/json;charset=utf-8

The controller who has a route to /api/user/myroles is decorated with a plain [Authorize] with no parameters.

I feel like I'm missing something in both auth and resource servers, but don't know what they are.

The other questions that ask "how to validate token issued by AspNet.Security.OpenIdConnect.Server" don't have an answer. I would appreciate some help in this.

Also, I've noticed that there is OAuth Introspection commented out in the sample provider, and have read somewhere that Jwt is not going to be supported soon. I can't find the dependency that gives me the OAuth Instrospection.


UPDATE I've included both of my startup.cs, from each of auth and resource servers. Could there be anything wrong that would cause the resource server to always return a 401 for every request?

One thing I didn't really touch throughout this whole endeavor is signing. It seems to generate a signature for the JWT at the auth server, but the resource server (I guess) doesn't know the signing keys. Back in the OWIN projects, I had to create a machine key and put on the two servers.

superjos
  • 12,189
  • 6
  • 89
  • 134
Mickael Caruso
  • 8,721
  • 11
  • 40
  • 72

1 Answers1

3

Edit: the order of your middleware instances is not correct: the JWT bearer middleware must be registered before MVC:

app.UseIISPlatformHandler();

app.UseJwtBearerAuthentication(options => {
    options.Audience = "my_resource_server";  
    options.Authority = "http://localhost:60556/"; 
    options.AutomaticAuthenticate = true;
    options.RequireHttpsMetadata = false;
});

app.UseMvc();
app.UseWelcomePage();

Sure enough, when I have this HTTP request, I do get an access token, but I'm not sure if that access token has all the data that the resource server expects.

Your authorization server and resource server configuration look fine, but you're not setting the "destination" when adding your claims (don't forget that to avoid leaking confidential data, AspNet.Security.OpenIdConnect.Server refuses to serialize the claims that don't explicitly specify a destination):

var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.Name, user.UserName, destination: "id_token token");

(await UM.GetRolesAsync(user)).ToList().ForEach(role => {
    identity.AddClaim(ClaimTypes.Role, role, destination: "id_token token");
});

Also, I've noticed that there is OAuth Introspection commented out in the sample provider, and have read somewhere that Jwt is not going to be supported soon. I can't find the dependency that gives me the OAuth Instrospection.

Starting with the next beta (ASOS beta5, not yet on NuGet.org when writing this answer), we'll stop using JWT as the default format for access tokens, but of course, JWT will still be supported OTB.

Tokens now being opaque by default, you'll have to use either the new validation middleware (inspired from Katana's OAuthBearerAuthenticationMiddleware) or the new standard introspection middleware, that implements the OAuth2 introspection RFC:

app.UseOAuthValidation();

// Alternatively, you can also use the introspection middleware.
// Using it is recommended if your resource server is in a
// different application/separated from the authorization server.
// 
// app.UseOAuthIntrospection(options => {
//     options.AutomaticAuthenticate = true;
//     options.AutomaticChallenge = true;
//     options.Authority = "http://localhost:54540/";
//     options.Audience = "resource_server";
//     options.ClientId = "resource_server";
//     options.ClientSecret = "875sqd4s5d748z78z7ds1ff8zz8814ff88ed8ea4z4zzd";
// });

You can find more information about these 2 middleware here: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/185

Community
  • 1
  • 1
Kévin Chalet
  • 39,509
  • 7
  • 121
  • 131
  • I've never heard of the concept of destination for claims. I'll go ahead and try to add that and see what happens. I'll also check into the instrospection middleware. – Mickael Caruso Dec 25 '15 at 21:56
  • So I understand that for now, the OpenIdConnectServer generates a JWT, correct, so it's correct to use JwtBearerAuthentication? Even with the destination specified for the claims, I'm still getting a 401. Debugging doesn't even get to my controller action, so I must not be specifying something. – Mickael Caruso Dec 26 '15 at 02:21
  • If you're using the latest version from NuGet.org, then yes, JWT is still the default format. You should post your entire Startup class (to make sure things are in the right order). You should also enable logging and post the debug traces. – Kévin Chalet Dec 26 '15 at 02:28
  • How do I enable logging? I'm only running debugs of my apps. How can I see why I'm getting a 401? I'm trying to set breakpoints and attempting to step into functions, but the 401 seems to beat me to where I need to go. – Mickael Caruso Dec 26 '15 at 13:01
  • See https://docs.asp.net/en/latest/fundamentals/logging.html and https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/vNext/samples/Mvc/Mvc.Server/Startup.cs#L46-L48 – Kévin Chalet Dec 26 '15 at 13:06
  • @MickaelCaruso I updated my answer to mention that your middleware are not in the right order. For your question regarding the signing key, don't miss this other SO post: http://stackoverflow.com/a/34466461/542757. – Kévin Chalet Dec 26 '15 at 16:34
  • OK. Thank you very much! I would have never known that the OAuth middleware must come before .UseMvc(), which now actually makes sense. And then, I also didn't know that the resource server "contacts" the auth server, so both have to be running at the same time. Again, thank you! And bigger thank you to creating this library! – Mickael Caruso Dec 26 '15 at 21:57
  • Glad you finally sorted it out and thanks for the kind words! :) – Kévin Chalet Dec 26 '15 at 22:52