简体   繁体   中英

Best practices for managing Web API JWT token in another Web API

My Project:

Web API project - ASP .NET Framework 4.8

Problem?

The code flow is as follows:

1.) The API is called -> it must call another API -> 2.) Get JWT authentication token -> 3.) Calls the desired method.

The problem is if my API is called 100 times, I will make 100 calls for the GetJwtToken() method and another 100 for the desired method itself, which seems like an overhead on the auth server. The token itself has a lifespan of 2 hours.

Are there any documented best practices on how to manage a Web API JWT token in another Web API?

What have I tried?

I've tried the following solutions and I'm still not sure whether they could be considered good practices.

  • One static class with two static properties Token and ValidTo and one static method GetJwtToken() that updates those properties. Before each call to the desired external API method, we check the ValidTo property and update the Token value if it has expired, via the static method.
  • In our service, we have one static private field Token .The method that calls the external API method is surrounded by a try catch blocks. The Catch(WebException ex) expects an Unauthorized exception if the token has expired. I check for HTTP Status Code 401 - Unauthorized.
if (response.StatusCode == HttpStatusCode.Unauthorized)

In case we go into that if clause we update the Token property by calling the GetJwtToken() method inside the catch block and then calling recursively the method again. In this way, we update the token only when it has expired and an unauthorized exception was thrown.

  • Another idea that I got, but didn't test is ActionFilterAttribute with overridden OnActionExecuting(HttpActionContext actionContext) method. Before we go into the Web API controller the action attribute has already checked whether we have Token and if it has expired. The problem here was I am not sure where to save the Token property. Possibly as a static value in another class.

Are there any other ways to manage a JWT Token of a Web API inside another Web API and what is considered best practices?
Some code snippets, pseudo-code, or articles would be appreciated.


Edit1:
I've read this question, but it doesn't help me, since it's about how to manage the token on the front end part. The project here is Web API it's all on the server-side.
Edit2:
Edited some sentences here and there so it's more readable.
Edit3:
Added one more option that I thought about.

I will expand my comments to answer because of the characters limit.

First, re-consider / re-examine why do you need to call the auth server for every API call? Do you have a data store of some kind like a database, a cache (in memory or remote), a Azure blob storage or a shared folder? If you have, you could consider persist your access tokens to your choice of data store.

Now, let's deal with token expiration time. Depends on how the external API grants the access tokens (I assume it is OAuth2 here), you usually could access the expiration time of a token, for example using expires_in in the response . The expires_in is equal to seconds since the unix epoch, so you should know when the token will expire. You could then save the token granted to your data store along with their expiration time and refresh token. When you use cache, you could set the cache entry to expire minutes before the token in it expires.

When you get next API call, check if you have a "valid" token from your data store. If no, call to get new JWT token and persist it using above method. Otherwise, try make API call with the token from your data store. If you have a background service, like a WebJob or Hangfire, you could periodically validate all tokens against the token validation endpoint (if your external API provides one) and refresh them when needed.

You should always handle unauthorized responses. Tokens can be revoked before they expire. In the case of unauthorized response received at your end, you could try re-authenticate with the external API and refresh the token kept in your data store. If the token generation needs to get user involved, you could return 401 to your client.

Lastly, you will also need to consider security. When you persist the tokens, even to your own data store, you need to encrypt them. This is for ASP.NET Core, but still worth reading it and do something similar in your API.

I'd handle this in some kind of BaseApiService

public class BaseApiService
{
    private readonly IHttpClientFactory httpClientFactory;
    private readonly ITokenHandler tokenHandler;

    public BaseApiService(IHttpClientFactory httpClientFactory, ITokenHandler tokenHandler)
    {
        this.httpClientFactory = httpClientFactory;
        this.tokenHandler = tokenHandler;
    }

    protected async Task<HttpResponseMessage> RequestAsync(HttpRequestMessage requestMessage)
    {
        var httpClient = httpClientFactory.CreateClient();
        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenHandler.Token);
        var response = await httpClient.SendAsync(requestMessage);
        
        if (!response.IsSuccessStatusCode)
        {
            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                var token = await tokenHandler.UpdateTokenAsync();
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
                return await RequestAsync(requestMessage);
            }
        }

        return response;
    }
}

Which would be responsible for making request, response serialization (notice I've used string responses for simplicity sake) and handling token for each request. Also you might want to consider handling errors and also handle infinite loops because it's currently calling self (eg on second call if it's unauthorized again, exit with error).

Token handler would be defined as singleton in DI and this is implementation

public interface ITokenHandler
{
    string Token { get; }
    Task<string> UpdateTokenAsync();
}

public class TokenHandler : ITokenHandler
{
    private readonly IHttpClientFactory httpClientFactory;
    public string Token { get; private set; } 

    public TokenHandler(IHttpClientFactory httpClientFactory)
    {
        this.httpClientFactory = httpClientFactory;
    }
    
    public async Task<string> UpdateTokenAsync()
    {
        var httpClient = httpClientFactory.CreateClient();
        var result = await httpClient.PostAsync("/external-api/token", new FormUrlEncodedContent(new []
        {
            new KeyValuePair<string, string>("username", "external-admin"),
            new KeyValuePair<string, string>("password", "external-password"),
        }));

        // or handle it however you want
        var token = result.IsSuccessStatusCode
            ? await result.Content.ReadAsStringAsync()
            : null;

        if (!String.IsNullOrEmpty(token))
        {
            Token = token;
        }

        return Token;
    }
}

And this is how you'd consume your BaseApiService

public class TodoService : BaseApiService
{
        public TodoService(IHttpClientFactory httpClientFactory, ITokenHandler tokenHandler) 
        : base(httpClientFactory, tokenHandler)
    {
    }

    public async Task<string> GetTodoAsync(int id)
    {
        var response = await RequestAsync(new HttpRequestMessage(HttpMethod.Get, $"/todo/{id}"));
        return await response.Content.ReadAsStringAsync();
    }
}

I don't think you need to add any ValidTo logic, but just rely on your Unauthorized response from 3rd party API, because you'll just complicate your code and you'll have to handle Unauthorized responses anyway.

Only thing is that you might lock getting/setting of token from TokenHandler , but this is just a basic example to show an idea how I'd implement it.

Perhaps consider you API to save the AuthToken (stateful).

I'm confused though of your current flow, usually you'll have an AuthApi that has the function to provide AuthorizationTokens .

Once the caller has the AuthToken it should save it; for front-end as you know, local storage, session storage, and cookies are considered, for backend or an API you could consider a stateful API which will save the Token in a global state, so it can append it to every request without going back-and-forth with your AuthApi (It will do a trip when the token expires though, etc.).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM