10

Updated:

Pinpoint helped me get this prototype off the launch pad - I was very close except for:

  • I need to upgrade to the beta6 SDK as per these instructions. Global.json now appears as follows:
{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-beta6"
  }
}
  • I updated references in project.json:
{
  "webroot": "wwwroot",
  "version": "1.0.0-*",

  "dependencies": {
    "Microsoft.AspNet.Mvc": "6.0.0-beta6",
    "Microsoft.AspNet.Server.IIS": "1.0.0-beta6",
    "Microsoft.AspNet.Server.WebListener": "1.0.0-beta6",
    "Microsoft.AspNet.StaticFiles": "1.0.0-beta6",
    "System.IdentityModel.Tokens": "5.0.0-beta6-207211625",
    "Serilog.Framework.Logging": "1.0.0-beta-43",
    "Microsoft.AspNet.Authentication.OAuthBearer": "1.0.0-beta6"
  },

  "commands": {
    "web": "Microsoft.AspNet.Hosting --config hosting.ini"
  },

  "frameworks": {
    "dnx451": { }
  },

  "exclude": [
    "wwwroot",
    "node_modules",
    "bower_components"
  ],
  "publishExclude": [
    "node_modules",
    "bower_components",
    "**.xproj",
    "**.user",
    "**.vspscc"
  ]
}
  • The middleware order in startup's Configure method matters. UseOAuthBearerAuthentication needs to come before UseMvc. The Configure method in Startup.cs now appears as follows:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseOAuthBearerAuthentication();      

    app.UseMvc();
}

I'm working with ASP.NET 5 and trying to implement an extremely simple proof of concept for generating and consuming JWT tokens. I have read the articles here, here and here but this one most closely matches my needs.

To this end, I very carefully read the article, re-read it, internalized all the comments and then stood up a simple example. I can now generate a JWT token, but when I attempt to call my controller action which has been decorated with an authorize attribute [Authorize("Bearer")], I receive the following message:

The following authentication scheme was not accepted: Bearer

As I have not seen a high-fidelity A-to-Z example on how to do this, consider the following steps to reproduce:

  • Create a new Web API Project in Visual Studio 2015 (I am using Enterprise) by selecting "New Project...Web...ASP.NET Web Application" and then the "Web API" option under the "ASP.NET 5 Preview Templates"
  • Using the beta 5 SDK, global.json looks like this:
{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-beta5",
    "runtime": "clr",
    "architecture": "x86"
  }
}
  • Bringing in the dependencies necessary for JWT tokens, project.json looks like this:
{
    "webroot": "wwwroot",
    "version": "1.0.0-*",

    "dependencies": {
        "Microsoft.AspNet.Mvc": "6.0.0-beta6",
        "Microsoft.AspNet.Server.IIS": "1.0.0-beta6",
        "Microsoft.AspNet.Server.WebListener": "1.0.0-beta6",
        "System.IdentityModel.Tokens": "5.0.0-beta5-206011020",
        "Microsoft.AspNet.Authentication.OAuthBearer": "1.0.0-beta5"
    },

    "commands": {
        "web": "Microsoft.AspNet.Hosting --config hosting.ini"
    },

    "frameworks": {
        "dnx451": { }
    },

    "exclude": [
        "wwwroot",
        "node_modules",
        "bower_components"
    ],
    "publishExclude": [
        "node_modules",
        "bower_components",
        "**.xproj",
        "**.user",
        "**.vspscc"
    ]
}
  • Startup.cs (this is an example not intended for production)
public class Startup
{
    const string        _TokenIssuer        = "contoso.com"             ;
    const string        _TokenAudience      = "contoso.com/resources"   ;
    RsaSecurityKey      _key                = null                      ;
    SigningCredentials  _signingCredentials = null                      ;

    public Startup(IHostingEnvironment env)
    {
        GenerateRsaKeys();
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddInstance(_signingCredentials);

        services.ConfigureOAuthBearerAuthentication
        (
            options =>
            {
                options.AutomaticAuthentication = true;
                options.TokenValidationParameters.IssuerSigningKey  = _key          ;
                options.TokenValidationParameters.ValidAudience     = _TokenAudience;
                options.TokenValidationParameters.ValidIssuer       = _TokenIssuer  ;
            }
        );

        services.ConfigureAuthorization
        (
            options =>
            {
                options.
                AddPolicy
                (
                    "Bearer",
                    new AuthorizationPolicyBuilder().
                        AddAuthenticationSchemes(OAuthBearerAuthenticationDefaults.AuthenticationScheme).
                        RequireAuthenticatedUser().
                        Build()
                );
            }
        );

        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env,ILoggerFactory loggerfactory)
    {
        app.UseMvc();

        app.UseOAuthBearerAuthentication();
    }

    void GenerateRsaKeys()
    {
        using(RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048))
        {
            _key = new RsaSecurityKey(rsa.ExportParameters(true));

            _signingCredentials = 
                new SigningCredentials
                (
                    _key                                    , 
                    SecurityAlgorithms.RsaSha256Signature   , 
                    SecurityAlgorithms.Sha256Digest         ,
                    "secret"
                );

            rsa.PersistKeyInCsp = false;
        }
    }
}
  • Some models:

Credentials.cs

public class Credentials
{
    public string user { set;get;}
    public string password { set;get;}
}

JwtToken.cs

public class JwtToken
{
    public string access_token  { set; get; }
    public string token_type    { set; get; }
}
  • A token controller for fetching a token (this is an example not intended for production), TokenController.cs:
[ Route("[controller]") ]
public class TokenController : Controller
{
    private readonly OAuthBearerAuthenticationOptions   _bearerOptions      ;
    private readonly SigningCredentials                 _signingCredentials ;

    public TokenController
    (
        IOptions<OAuthBearerAuthenticationOptions>  bearerOptions       ,
        SigningCredentials                          signingCredentials
    )
    {
        _bearerOptions      = bearerOptions.Options ;
        _signingCredentials = signingCredentials    ;
    }

    // POST: /token
    [HttpPost()]
    public JwtToken Token([FromBody] Credentials credentials)
    {
        // Pretend to validate credentials...

        JwtSecurityTokenHandler handler = 
            _bearerOptions                      .
            SecurityTokenValidators             .
            OfType<JwtSecurityTokenHandler>()   .
            First();

        JwtSecurityToken securityToken = 
            handler     .
            CreateToken
            (
                issuer              : _bearerOptions.TokenValidationParameters.ValidIssuer  ,
                audience            : _bearerOptions.TokenValidationParameters.ValidAudience,
                signingCredentials  : _signingCredentials                                   ,
                subject             : new ClaimsIdentity
                (
                    new Claim [] 
                    {
                        new Claim(ClaimTypes.Name,"somebody"),
                        new Claim(ClaimTypes.Role,"admin"   ),
                        new Claim(ClaimTypes.Role,"teacher" ),
                    }
                ) ,
                expires             : DateTime.Today.AddDays(1)
            );

        string token = handler.WriteToken(securityToken);

        return new JwtToken()
        {
            access_token    = token     ,
            token_type      = "bearer"
        };
    }
}
  • A values controller to demonstrate ingesting the token, ValuesController.cs:
[Route("api/[controller]")]
public class ValuesController : Controller
{
    // GET: api/values
    [Authorize("Bearer")]
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET api/values/5
    [HttpGet("{id}")]
    public string Get(int id)
    {
        return "value";
    }
}
  • Fire up a copy of postman (or your favorite REST client), launch the sample app under Visual Studio and make a POST request, similar to http: // localhost:22553/token/ with the JSON body:
{
    "user" : "user",
    "password" : "secret"
}

The app responds with a token:

{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6bnVsbH0.eyJ1bmlxdWVfbmFtZSI6InNvbWVib2R5Iiwicm9sZSI6WyJhZG1pbiIsInRlYWNoZXIiXSwiaXNzIjoiY29udG9zby5jb20iLCJhdWQiOiJjb250b3NvLmNvbS9yZXNvdXJjZXMiLCJleHAiOjE0Mzk1MzU2MDB9.anRgL10XFG_bKDDxY3D2xQSfhPRLGMjUTreQNsP1jDA6eRKwXHf3jtpCwm_saoWyUDFFA2TMI9e_LbP6F5l7vtozCluziE_GQkPkspUSWuWIpQJLPRTTPPZHGKmPmK4MLEl1zPPrggJWbvF9RBw3mMQ0KoMfjSL0vUQ8kZ7VXAel8dnYJccd-CFdnB6aDe79x2E9Se2iLxdhr--R_qgvfz1Fa6tR1dstqLQ-UjYqPWY4SOgBjM3abtjfLLVEzeQMVyezX7Cx9ObMXAGbGvQL6GB_T5RlfAoXWME4jM8Bzhd-07wwd732bBws4OXivj1sSz-qawNTnXmnuccLRtI1uA",
  "token_type": "bearer"
}
  • Copy the token from the previous POST, then in postman make a GET request similar to http: // localhost:22553/api/values, taking care to add an Authorization header with the value "bearer YOURTOKEN" (e.g. bearer eyJ0eXAiOiJKV1QiLCJ...)

  • Observe that the app responds with the error:

System.InvalidOperationException The following authentication scheme was not accepted: Bearer

Stack trace as follows:

at Microsoft.AspNet.Http.Authentication.Internal.DefaultAuthenticationManager.< AuthenticateAsync> d__9.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Http.Authentication.AuthenticationManager.< AuthenticateAsync> d__2.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter< TResult> .GetResult() 
at Microsoft.AspNet.Mvc.AuthorizeFilter.< OnAuthorizationAsync> d__5.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Mvc.Core.FilterActionInvoker.< InvokeAuthorizationFilterAsync> d__43.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Mvc.Core.FilterActionInvoker.< InvokeAllAuthorizationFiltersAsync> d__42.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Mvc.Core.FilterActionInvoker.< InvokeAsync> d__40.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Mvc.MvcRouteHandler.< InvokeActionAsync> d__4.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Mvc.MvcRouteHandler.< RouteAsync> d__3.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Mvc.Routing.InnerAttributeRoute.< RouteAsync> d__10.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Routing.RouteCollection.< RouteAsync> d__9.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Builder.RouterMiddleware.< Invoke> d__4.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Hosting.Internal.RequestServicesContainerMiddleware.< Invoke> d__3.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Hosting.Internal.HostingEngine.< > c__DisplayClass29_0.< < Start> b__0> d.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Loader.IIS.RuntimeHttpApplication.< ProcessRequestAsyncImpl> d__10.MoveNext() 
--- exception rethrown --- 
at Microsoft.AspNet.Loader.IIS.RuntimeHttpApplication.< ProcessRequestAsyncImpl> d__10.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Loader.IIS.HttpApplicationBase.< InvokeProcessRequestAsyncImpl> d__9.MoveNext()

Note that adding logging adds virtually no additional insight, as the following logs show:

2015-08-13 13:32:35.969 -07:00 [Information] Request successfully matched the route with name 'null' and template '"api/Values"'.
Exception thrown: 'System.InvalidOperationException' in Microsoft.AspNet.Http.dll
2015-08-13 13:32:36.247 -07:00 [Error] An error occurred while handling the request.
2015-08-13 13:32:36.247 -07:00 System.InvalidOperationException: The following authentication scheme was not accepted: Bearer

I am hoping someone might understand where the breakdown in this example is taking place.

Community
  • 1
  • 1
42vogons
  • 683
  • 7
  • 19

1 Answers1

6

You must register the OAuth2 bearer authentication middleware before MVC, or your users will be unauthenticated when reaching MVC:

public class Startup {
    public void Configure(IApplicationBuilder app) {
        app.UseJwtBearerAuthentication(new JwtBearerOptions {
            // Your JWT bearer options.
        });

        app.UseMvc();
    }
}
Kévin Chalet
  • 39,509
  • 7
  • 121
  • 131
  • Pinpoint thanks for your observation. I had previously tried this approach but assumed I made a mistake as I receive the following error when I attempt to call any of my controller methods: [Error] An error occurred while handling the request. System.TypeLoadException: Method 'ChallengeAsync' in type 'Microsoft.AspNet.Authentication.OAuthBearer.OAuthBearerAuthenticationHandler' from assembly 'Microsoft.AspNet.Authentication.OAuthBearer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation. – 42vogons Aug 14 '15 at 13:48
  • This error is likely caused by the fact you're referencing beta5 and beta6 packages in the same application. Make sure to migrate everything to beta6 (even the DNX runtime), and it should work. – Kévin Chalet Aug 14 '15 at 13:56
  • Thanks, Pinpoint - that was the exact answer. I upgraded my visual studio tooling as per [this article](https://github.com/aspnet/Announcements/issues/51) and I can now generate and ingest JWT tokens. I'm going to edit the original post to reflect these changes. – 42vogons Aug 14 '15 at 15:14
  • @Pinpoint, Does this work on beta8 for you? For beta8 I'm getting Status 500 and exception 'System.MissingMethodException: Method not found: '!!0 Microsoft.AspNet.Http.HttpContext.GetFeature()'.'. Any idea how to fix this? – AntonS Sep 02 '15 at 04:31
  • @AntonS make sure your beta8 packages all have the same nightly version. Massive breaking changes have been added to aspnet/HttpAbstractions. – Kévin Chalet Sep 02 '15 at 12:35
  • @Pinpoint, that didn't help me, so I will try to come back to beta6. I also have a question about `dnu publish` http://stackoverflow.com/questions/32338218/showing-dnu-publish-exceptions-in-teamcity-build-results. If you can take a look or point me to the person who is responsible or can know something about it I will appreciate. (Sorry, that asking about it in this flow) – AntonS Sep 04 '15 at 02:30
  • @Pinpoint, I'm back on this project and also unable to use beta8. The method 'tokenHandler.CreateToken(tokenDescriptor)' throws 'The method or operation is not implemented.'. How do I use the nightly builds as you recommend? I tried adding https://www.myget.org/F/aspnetvnext/api/v3/index.json to my NuGet.config and pulling in the "1.0.0-rc1-XXX" packages listed [here](https://www.myget.org/gallery/aspnetvnext). I suspect that's not what you meant, however, since the app won't even start with those packages. – 42vogons Oct 23 '15 at 22:33
  • @42vogons you should consider using `AspNet.Security.OpenIdConnect.Server` to generate your access tokens: http://stackoverflow.com/questions/33140360/getting-an-access-token-in-asp-net-5/33147208 – Kévin Chalet Oct 23 '15 at 22:46