Matthias Burger
Matthias Burger

Reputation: 5946

User-authorization on API-side with .net core

I'm writing a web-application in .net core that uses an API and a Website.

The web-service builds a JWT-token. This is the service-configuration (removed unnecessary parts)

public void ConfigureServices(IServiceCollection services)
{
    //...

    var tokenValidationParameters = new TokenValidationParameters
    {
        // The signing key must match!
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = signingKey,

        // Validate the JWT Issuer (iss) claim
        ValidateIssuer = true,
        ValidIssuer = "ExampleIssuer",

        // Validate the JWT Audience (aud) claim
        ValidateAudience = true,
        ValidAudience = "ExampleAudience",

        // Validate the token expiry
        ValidateLifetime = true,

        // If you want to allow a certain amount of clock drift, set that here:
        ClockSkew = TimeSpan.Zero,
    };

    var serialiser = services.BuildServiceProvider().GetService<IDataSerializer<AuthenticationTicket>>();

    var dataProtector = services.BuildServiceProvider().GetDataProtector(new string[] {$"IronSphere.Web.Site-Auth"});

    services
        .AddAuthentication(o =>
        {
            o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
            cfg.TokenValidationParameters = tokenValidationParameters;
        })
        .AddCookie(cookie =>
        {
            cookie.Cookie.Name = "access_token";
            cookie.TicketDataFormat = new JwtTokenValidator(
                SecurityAlgorithms.HmacSha256,
                tokenValidationParameters, serialiser, dataProtector);
        });

    //...
}

So far, so good. The login works, my website-side authorization works, I can work with the [Authorize] attribute.

The problem is now, I'm logged in on the website, but not on the API.

I can't use the [Authorize]-attribute for my API methods (and, yes, for sure, it makes sense).

So when I'm logged in, everytime I call the API, I also send the token in the header (this works, I can read it in the API-controller). I guess I'd need to deserialize it.

I tried with dependency injection to get that IDataSerializer<AuthenticationTicket> into my controllers with:

services.AddSingleton<IDataSerializer<AuthenticationTicket>>(services.BuildServiceProvider().GetService<IDataSerializer<AuthenticationTicket>>());

then take the key from header, deserialize and I'd have my user-object with claims. But when I try to inject in any controller it makes my application crash (just a message, that dotnet stopped working)

Any idea how I can also validate the user when the api is getting called? (I could post more code if you need, just didn't want to fill here with too much code)

Upvotes: 1

Views: 953

Answers (1)

Matthias Burger
Matthias Burger

Reputation: 5946

So after @Tseng helped me a lot with his input, here is my result (more input on how to do it better would be nice):

  1. Added the un-protector for the token as a service with adding in Startup.cs

    services.AddTransient<IJwtTokenService, JwtTokenService>();
    
  2. the IJwtTokenService

    public interface IJwtTokenService
    {
        string UnprotectToken(string protectedText);
    }
    
  3. the implemented JwtTokenService:

    public class JwtTokenService:IJwtTokenService
    {
        private readonly IDataSerializer<AuthenticationTicket> _ticketSerializer;
        private readonly IDataProtector _dataProtector;
    
        public JwtTokenService(IDataSerializer<AuthenticationTicket> serializer, IDataProtector protector)
        {
            _ticketSerializer = serializer;
            _dataProtector = protector;
        }
    
        public string UnprotectToken(string protectedText)
        {
            SecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(" ......... "));
    
            TokenValidationParameters tokenValidationParameters =
                _getTokenValidationParameters();
    
            JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
            AuthenticationTicket authTicket;
            string embeddedJwt;
    
            try
            {
                // logic to deserialize token
                // logic to validate token
                // more logic... (algorithm,..)
            }
            catch (Exception)
            {
                return null;
            }
    
            return embeddedJwt;
        }
    }
    
  4. So in the services I also had to add the IDataProtector. It threw an exception before.

    services.AddTransient(x => x.GetDataProtector(new[] {$"auth"}));
    
  5. Then I could add the IJwtTokenService into the constructor for dependency injection, unprotect it and sent it with the header:

    protected ServiceBase(
        IHttpContextAccessor contextAccessor, 
        IJwtTokenService jwtTokenService, 
        IMemoryCache memoryCache = null)
    {
        MemoryCache = memoryCache ?? new MemoryCache(new MemoryCacheOptions());
        CachingFunctionalty = new CachingFunctionality();
        HttpContextAccessor = contextAccessor;
        JwtTokenService = jwtTokenService;
    }
    
    protected RestClient CreateClient()
    {
        RestClient restClient = new RestClient(ServiceAdress);
    
        var token = HttpContextAccessor.HttpContext.Request.Cookies["access_token"];
    
        if (string.IsNullOrWhiteSpace(token)) return restClient;
    
        var unprotected = JwtTokenService.UnprotectToken(token);
        restClient.AuthenticationHeaderValue = new AuthenticationHeaderValue("Bearer", unprotected);
    
        return restClient;
    }
    

Now my API works together with the AuthorizeAttribute

Upvotes: 1

Related Questions