2

I'm trying to get JwtBearerAuthentication working in an ASP.Net 5, MVC6 WebApi application. I got the Jwt token generated at my token end point, but when I try to use the token to get access all the WebApi endpoint, I got the following error:

System.InvalidOperationException: IDX10803: Unable to obtain configuration from: localhost:5000/.well-known/openid-configuration.

Here is the code in my Startup.cs, can any one tell me what I did wrong.

public Startup(IHostingEnvironment env)
{
    const string _TokenIssuer = "http://localhost:5000/";
    const string _TokenAudience = "http://localhost:5000/";

public void ConfigureServices(IServiceCollection services)
    {
    ...
        RsaSecurityKey _signingKey = null;
        SigningCredentials signingCredentials = null;
        using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048))
        {
            signingKey = new RsaSecurityKey(rsa.ExportParameters(true));
            signingCredentials = new SigningCredentials(_SigningKey, SecurityAlgorithms.RsaSha256Signature);
        }
        services.AddInstance<SigningCredentials>(signingCredentials);  // tobe used in JwtTokenHandler in the Token Controller

    var jwtOptions = new JwtBearerOptions();
        jwtOptions.AutomaticAuthenticate = true;
        jwtOptions.TokenValidationParameters.IssuerSigningKey = signingKey;
        jwtOptions.TokenValidationParameters.ValidAudience = _TokenAudience;
        jwtOptions.TokenValidationParameters.ValidIssuer = _TokenIssuer;
        services.AddInstance<JwtBearerOptions>(jwtOptions);   //tobe used in the Token Controller

        services.AddAuthorization(auth =>
        {
            auth.AddPolicy(JwtBearerDefaults.AuthenticationScheme, new AuthorizationPolicyBuilder()
                .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
                .RequireAuthenticatedUser().Build());
        });

        services.AddMvc();
        services.AddAuthentication();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        ...
        //Add Bearer Authentication Middleware, make sure this is before UseIdentity line
        app.UseJwtBearerAuthentication(options =>
        {
            options.AutomaticAuthenticate = true;
            options.Audience = _TokenAudience;
            options.Authority = _TokenIssuer;
            options.RequireHttpsMetadata = false;
        });

        app.UseIdentity();
        app.UseMvc();
    }
}

I'm using Kestrel to host and my service url is http://localhost:5000/XXX


Edit: I'm still having problems with JwtBearerAuthentication to validate token. I switched to use OpenIdConnectServer to issue token. That part is fine. But when try to validate token using JwtBearerAuthentication, unless I set options.TokenValidationParameters.ValidateAudience = false; I always get SecurityTokenInvalidAudienceException: IDX10208: Unable to validate audience. validationParameters.ValidAudience is null or whitespace and validationParameters.ValidAudiences is null.

When I set it to false, I can hit my WebApi code, however, when I inspect the User object in the controller, the User.Identity.Name is not set. User.Identity.Authenticationtype="Authentication.Federation" and User.Identity.IsAuthenticated = true. I can see a list of Claims under the Identity, but not all the claims I added to the token are there. This is really different than what I have seen in .Net 4.5 with UseOAuthBearerAuthentication.

I have put my testing project in GitHub https://github.com/Sally-Xu/Net5Start. Could you see what is wrong?

Kévin Chalet
  • 39,509
  • 7
  • 121
  • 131
user3314216
  • 23
  • 1
  • 5

1 Answers1

3

The error you're seeing is caused by the fact you're setting Authority to a non-null value. This property should only be used if your tokens are issued by an OpenID Connect server like AspNet.Security.OpenIdConnect.Server. Since you're using a custom token controller, I guess it's not a real OAuth2/OpenID Connect server, which explains why you're seeing this error.

Stop setting this property and it should work. Note that you'll have to set the IssuerSigningKey directly in the UseJwtBearerAuthentication call, as it won't reuse the options set from ConfigureServices.

Community
  • 1
  • 1
Kévin Chalet
  • 39,509
  • 7
  • 121
  • 131
  • Thanks @Pinpoint for answering my question. I've tried your suggestion, still doesn't work. Now I'm getting a different error: IDX10503: Signature validation failed. – user3314216 Dec 21 '15 at 14:40
  • Hi @Pinpoint, I have seen your answers in many JwtAuthentication related threads and seems you are an expert in this area. Many code example you posted were based on pre-rc release, and they have changed a lot along the way. If you can write a blog to provide a working code walk-through based on the 1.0.0-rc1-final release, that would benefit a lot of developers. Thanks again for your help! – user3314216 Dec 21 '15 at 14:56
  • Hey! The signature validation error you're seeing is likely caused by the fact you're generating a new RSA key each time your app is started. You can see this other SO post for more information: http://stackoverflow.com/a/33147086/542757. I try to do my best to keep my answers up-to-date but if you see an outdated answer, please ping me so I can fix it. – Kévin Chalet Dec 21 '15 at 16:17
  • Hi @Pinpoint, I'm still having problems with JwtBearerAuthentication to validate token. See the extra lines in my original post. – user3314216 Dec 23 '15 at 17:24
  • @user3314216 http://stackoverflow.com/a/32801010/542757 and http://stackoverflow.com/a/33806784/542757 should put you on the right track. – Kévin Chalet Dec 23 '15 at 19:39
  • Thanks @Pinpoint, both links you provided helped. Looks like we have to SetResources on the ticket to indicate the audience of the ticket. Found SetAudiences on the ticket doesn't work which is strange and confusing. Also, there is an error in TokenEndpointResponse override if I want to add each ticket.Properties.Item to the context.Payload. I had to exclude the Property which has Key = "resource" to avoid error of "Can not add property resource to Newtonsoft.Json.Linq.JObject, Property with the same name already exists on object". – user3314216 Dec 25 '15 at 01:54
  • `SetAudiences` has no effect because it's not intended to be used in your own code (at least, not outside `Serialize*Token`): it's used internally by the OIDC middleware to set the audiences for each type of tokens (for identity tokens, the audience is the client, for access token, the audience is the resource server). `SetResources` is the only way to control the audiences of the access token from your own code. That said, I agree this can be confusing so please open a ticket on GitHub to make sure we improve that in the future builds. – Kévin Chalet Dec 25 '15 at 02:09
  • Concerning the JSON error you're seeing, it's not surprising: the OIDC server automatically returns a "resource" property when using the code flow or when the resources you grant are not asked by the client (using the `resource` OIDC parameter), so trying to add a new property with the same key necessarily results in an error. It's definitely not a bug (at least, not an ASOS bug). That said, copying the authentication properties is absolutely not a good idea: don't do that, as we may store confidential data in the properties. – Kévin Chalet Dec 25 '15 at 02:12
  • @Pintpoint, I'm not adding a Property with key = "resource" or "scope". I only used ticket.SetResources and ticket.SetScope; But I need to pass some non sensitive data back to my client with the ticket. That's why I used Properties to do that. Right now I have to do the following to avoid error: – user3314216 Dec 26 '15 at 02:00
  • public override Task TokenEndpointResponse(TokenEndpointResponseContext context) { //add custom Properties to the response payload, those properties are exposed to the client foreach (var property in context.AuthenticationTicket.Properties.Items) { if(property.Key != "resource" && property.Key !="scope") context.Payload.Add(property.Key, property.Value); } return Task.FromResult(null); } – user3314216 Dec 26 '15 at 02:00
  • Of course you are: you're copying all the authentication properties contained in the authentication ticket (that already has a "resource" authentication property attached by the server itself) to a `JObject` that also contains a `resource` JSON property: this cannot work. What you're trying to do is fragile and unsafe: **don't do that**. If you want to flow specific authentication properties from the ticket to the JSON payload, consider using a fixed list of properties or using a custom prefix to avoid collisions. – Kévin Chalet Dec 26 '15 at 02:14
  • Here is what I'm trying to do and how we did it in .Net4 with OAuth Bearer Tokens Generation : http://stackoverflow.com/questions/26357054/return-more-info-to-the-client-using-oauth-bearer-tokens-generation-and-owin-in . The only difference now is that I need to override TokenEndpointResponse instead of TokenEndpoint function. I thought the Ticket.Properties collection should only contain the extra key-value pairs which I don't want to put into the token. Isn't this what I should do? – user3314216 Dec 26 '15 at 03:17
  • It's still supported (though adding custom claims to the `id_token` is the recommended approach), but you shouldn't copy the authentication properties as-is in the JSON payload using a `foreach`. Just pick the property you need instead of copying everything. – Kévin Chalet Dec 26 '15 at 03:55
  • I don't see id_token in the response, only access_token. Also how to generate RefreshToken in OIDC? OAuthAuthorizationServerOptions used to have RefreshTokenProvider, and I don't find the same property in the OpenIdConnectServerOptions. – user3314216 Dec 27 '15 at 18:58
  • You should really open separate questions. To retrieve an identity token, you must add `openid` to the `scope` parameter. To retrieve a refresh token, add `offline_access` (http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess). The `RefreshTokenProvider` no longer exists (replaced by the `SerializeRefreshToken` and `DeserializeRefreshToken` events), but the refresh token is automatically generated, so don't worry about that. – Kévin Chalet Dec 27 '15 at 19:02