ASP.NET jwt with AuthFeature fails servicestack authentication

Hi all

We are integrating Servicestack V8.6 with an existing OIDC provider. I have implemented the addServices according to the documentation provided here: JWT Identity Auth

Source programm.cs

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
    {        
        using var client = new HttpClient();

        var discoveryDocumentAddress = builder.Configuration["Identity:DiscoveryDocument"];
        var audience = builder.Configuration["Identity:Audience"];

        var discoveryDocument = client.GetDiscoveryDocumentAsync(discoveryDocumentAddress).Result;
        var key = discoveryDocument.KeySet.Keys.First(k => k.Alg == OidcConstants.Algorithms.Asymmetric.RS256);

        var publicKey = new RSAParameters
        {
            Modulus = WebEncoders.Base64UrlDecode(key.N),
            Exponent = WebEncoders.Base64UrlDecode(key.E)
        };
        
        options.Audience = "AUDIENCE";
        options.Authority = "https://AUTHORITY";
        
        options.TokenValidationParameters = new TokenValidationParameters
        {
            IssuerSigningKey = new RsaSecurityKey(publicKey),
            ValidAudience = audience,
            ValidIssuer = "ISSUER",
        };
    });

In Configure.Authentication.cs I have added the Authentication feature:

public void Configure(IWebHostBuilder builder)
  {
      builder.ConfigureServices((context, services) =>
      {
          services.AddPlugin(new AuthFeature(IdentityAuth.For<IdentityUser>(
              // Configure ServiceStack's Integration with Identity Auth
              options =>
              {
                  options.SessionFactory = () => new AuthUserSession();
                  options.JwtAuth();
              })));
      });
  }

If I try to login with a valid JWT Token in API Explorer (API Endpoint api/Authenticate), I get an HTTP 401 Unauthorised although, ASPNetCore.Authentication Middleware reports the Token to be valid

08:53:33 DBG]:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler Successfully validated the token.
[08:53:33 DBG]:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler AuthenticationScheme: Bearer was successfully authenticated.

What am I missing?

Full Log stack:

[08:53:33 INF]:Microsoft.AspNetCore.Hosting.Diagnostics Request starting HTTP/1.1 POST http://localhost:5000/api/Authenticate - application/json 2
[08:53:33 DBG]:Microsoft.AspNetCore.Routing.Matching.DfaMatcher 1 candidate(s) found for the request path '/api/Authenticate'
[08:53:33 DBG]:Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware Request matched endpoint 'HTTP: POST /api/Authenticate'
[08:53:33 DBG]:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler Successfully validated the token.
[08:53:33 DBG]:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler AuthenticationScheme: Bearer was successfully authenticated.
[08:53:33 INF]:Microsoft.AspNetCore.Routing.EndpointMiddleware Executing endpoint 'HTTP: POST /api/Authenticate'
[08:53:33 DBG]:ServiceStack.Host.Handlers.HttpAsyncTaskHandler CreateContentTypeRequest/hasContentBody:True:POST:application/json:2:
[08:53:33 DBG]:Microsoft.AspNetCore.Server.Kestrel Connection id "0HNAN8MEQA9H2", Request id "0HNAN8MEQA9H2:00000003": started reading request body.
[08:53:33 DBG]:Microsoft.AspNetCore.Server.Kestrel Connection id "0HNAN8MEQA9H2", Request id "0HNAN8MEQA9H2:00000003": done reading request body.
[08:53:33 INF]:Microsoft.AspNetCore.Routing.EndpointMiddleware Executed endpoint 'HTTP: POST /api/Authenticate'
[08:53:33 DBG]:Microsoft.AspNetCore.Server.Kestrel.Connections Connection id "0HNAN8MEQA9H2" completed keep alive response.
[08:53:33 INF]:Microsoft.AspNetCore.Hosting.Diagnostics Request finished HTTP/1.1 POST http://localhost:5000/api/Authenticate - 401 0 null 112.9998ms

Note this is using ASP .NET Identity JWT Authentication, we don’t provide support for integration with external third party Authentication providers.

You should ensure you have a working JWT configuration with your ASP .NET App before using ServiceStack, e.g. can you verify you can access a protected Minimal API with your JWT Token?

app.MapGet("/hello", [Authorize] () => "Hi");

I can confirm that a simple /hello request works perfectly

[09:38:23 DBG]:Microsoft.AspNetCore.Routing.Matching.DfaMatcher 1 candidate(s) found for the request path '/hello'
[09:38:23 DBG]:Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware Request matched endpoint 'HTTP: GET /hello'
[09:38:23 DBG]:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler Successfully validated the token.
[09:38:23 DBG]:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler AuthenticationScheme: Bearer was successfully authenticated.
[09:38:23 DBG]:Microsoft.AspNetCore.Authorization.DefaultAuthorizationService Authorization was successful.
[09:38:23 INF]:Microsoft.AspNetCore.Routing.EndpointMiddleware Executing endpoint 'HTTP: GET /hello'
[09:38:23 INF]:Microsoft.AspNetCore.Routing.EndpointMiddleware Executed endpoint 'HTTP: GET /hello'
[09:38:23 DBG]:Microsoft.AspNetCore.Server.Kestrel.Connections Connection id "0HNANQCQEBLBS" completed keep alive response.
[09:38:23 INF]:Microsoft.AspNetCore.Hosting.Diagnostics Request finished HTTP/1.1 GET http://localhost:5000/hello - 200 null text/plain; charset=utf-8 9.0069ms
[09:38:23 DBG]:Microsoft.AspNetCore.Server.Kestrel Connection id "0HNANQCQEBLBS", Request id "0HNANQCQEBLBS:00000002": started reading request body.
[09:38:23 DBG]:Microsoft.AspNetCore.Server.Kestrel Connection id "0HNANQCQEBLBS", Request id "0HNANQCQEBLBS:00000002": done reading request body.

We have been using an IdentityServer4 as our external OIDC provider for years with servicestack in a non minimalistic API Setup and it worked perfectly.

With the Setup of a new project we wanted to update to the latest ASPNet Core standards

I’m assuming you’re using Endpoint Routing?

If so can you try to access a protected Hello ServiceStack API by adding [ValidateIsAuthenticated] to the included Hello API:

[ValidateIsAuthenticated]
[Route("/hello/{Name}")]
public class Hello : IGet, IReturn<HelloResponse>
{
    public string? Name { get; set; }
}

Yes, we are using endpoint mapping.

The request returns an authorised exception:

[09:51:43 DBG]:Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware Request matched endpoint 'HTTP: GET /hello/{Name}'
[09:51:43 DBG]:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler Successfully validated the token.
[09:51:43 DBG]:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler AuthenticationScheme: Bearer was successfully authenticated.
[09:51:43 DBG]:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler AuthenticationScheme: Bearer was successfully authenticated.
[09:51:43 DBG]:Microsoft.AspNetCore.Authorization.DefaultAuthorizationService Authorization was successful.
[09:51:43 INF]:Microsoft.AspNetCore.Routing.EndpointMiddleware Executing endpoint 'HTTP: GET /hello/{Name}'
[09:51:43 DBG]:ServiceStack.Host.Handlers.HttpAsyncTaskHandler CreateRequestAsync/requestParams:
[09:51:43 DBG]:ServiceStack.Host.Handlers.HttpAsyncTaskHandler CreateContentTypeRequest/hasContentBody:True:GET:application/json:66:
[09:51:43 DBG]:Microsoft.AspNetCore.Server.Kestrel Connection id "0HNANQKCGR22J", Request id "0HNANQKCGR22J:00000003": started reading request body.
[09:51:43 DBG]:Microsoft.AspNetCore.Server.Kestrel Connection id "0HNANQKCGR22J", Request id "0HNANQKCGR22J:00000003": done reading request body.
[09:51:43 ERR]:ServiceStack.DtoUtils Not Authenticated
ServiceStack.HttpError: Not Authenticated
   at ServiceStack.TypeValidator.ThrowIfNotValidAsync(Object dto, IRequest request) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/TypeValidators.cs:line 346
   at ServiceStack.Validators.AssertTypeValidatorsAsync(IRequest req, Object requestDto, Type requestType) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Validators.cs:line 72
   at ServiceStack.Validation.ValidationFilters.RequestFilterAsync(IRequest req, IResponse res, Object requestDto, Boolean treatInfoAndWarningsAsErrors) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Validation/ValidationFilters.cs:line 27
   at ServiceStack.Validation.ValidationFilters.RequestFilterAsync(IRequest req, IResponse res, Object requestDto) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Validation/ValidationFilters.cs:line 15
   at ServiceStack.ServiceStackHost.ApplyRequestFiltersSingleAsync(IRequest req, IResponse res, Object requestDto) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/ServiceStackHost.Runtime.cs:line 217
   at ServiceStack.ServiceStackHost.ApplyRequestFiltersAsync(IRequest req, IResponse res, Object requestDto) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/ServiceStackHost.Runtime.cs:line 145
   at ServiceStack.Host.RestHandler.ProcessRequestAsync(IRequest req, IResponse httpRes, String operationName) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Host/RestHandler.cs:line 93
[09:51:43 INF]:Microsoft.AspNetCore.Routing.EndpointMiddleware Executed endpoint 'HTTP: GET /hello/{Name}'
[09:51:43 DBG]:Microsoft.AspNetCore.Server.Kestrel.Connections Connection id "0HNANQKCGR22J" completed keep alive response.
[09:51:43 INF]:Microsoft.AspNetCore.Hosting.Diagnostics Request finished HTTP/1.1 GET http://localhost:5000/hello/hi - 401 null application/json; charset=utf-8 85.0132ms

Can you see if changing it to use [Authorize] instead makes a difference, e.g:

[Authorize]
[Route("/hello/{Name}")]
public class Hello : IGet, IReturn<HelloResponse>
{
    public string? Name { get; set; }
}
1 Like

Yes! this works. This will solve our issue regarding the API working at all. thanks!

Do you have an idea for using the API Explorer Feature? I still get an unauthorised exception there

Not yet, will need to investigate further.

Can you try again after removing this line? The default SessionFactory uses a Session with an IRequireClaimsPrincipal which is needed for Identity Auth providers.

3 Likes

Hi
Yes! This was it. As far as I have tested, everything we need is working perfectly.

thanks a lot for your help!

1 Like