Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reusing a bearer token for multiple API calls

I have an architecture where by I have an initial ASP MVC landing page which calls into a Web API service which inturn calls into 2 others which themselves have do also.

Currently authentication is handled via windows authentication user user/roles.

I'd like to obtain a identity server token on arrival at the aspmvc aspect (using windows authentication still) and then return a token with suitable claims/scope that I can reuse it for all subsequent calls by extracting and passing along the line.

Is this possible? What is the preferred or best practice here? Perhaps I would be using the server to server flow for each leap.. but it seems like follow getting another token.. and where would I even keep them within the inner dolls??

like image 235
Jon H Avatar asked Sep 19 '17 19:09

Jon H


People also ask

Can Bearer tokens be reused?

The MVC app owns the token and sends it as Bearer to an api, that api can simply re-send the same bearer token to any api's it consumes, and so on...

Can the authentication token be reused for many requests?

The same token can be reused.

How many times an access token can be used?

It depends... by default, each time you refresh token, it returns new access token and new refresh token. If you're talking about old refresh token, it only available one time. But from client side, there is no limitation, you can always refresh as soon as the refresh token is not expired.

Can OAuth token be reused?

Answer to the question "Should I reuse OAuth 2.0 access tokens?" Yes, the token is supposed to be used as many times as you need within the given expiry time (google sets it to 1 hour). After it has expired, use the refresh token to get another access token and use it as many times as you need.


1 Answers

UPDATED - After discussing with Matt G, I added a better explanation to my answer in order to be clear on my point. I reckon I wasn't clear enough at the beginning.

UPDATE 2 - Adding point 5

I think that a token should be issued for one client and must be used only by that specific client to access all the resources it asked access for.

Case

  • Api1 asks for a token and can access Api2, Api3, Api4, Api5.
  • Api2 uses Api1's token and have the access to the same resources as Api1.

Comments

  1. It means that Api2 can access Api3, Api4, Api5. But what happens if Api2 shouldn't be granted access for Api5? Now you have problems. As soon as this situation show up, you have to redesign your security mechanism.

  2. In addition, it means that the token sent to an Api2 contains scopes that are not relevant to it which sounds like a bit strange for me.

  3. In the other hand, a scope for Api1 may mean something different for Api2 which can lead to misunderstandings. But this will depend on your development.

  4. If you do authentication and authorization using Scopes you shouldn't be sharing your token because Api1 can execute code that for example Api2 shouldn't execute and this is a security issue.

  5. If Api1 is the one that asks for the token to the IdP. What happen to Api2 if you want to used it separately from Api1? it can't do calls to others Apis because Api1 did not pass it the token? Or all the Apis have the ability to ask for tokens to the IdP and all of them pass the token through to the others Apis depending on which Api did the first call? Are you probably putting more complexity than need it?

What you are trying to achieve is doable but for me it's not a good idea.

Below I propose you an alternative solution to this problem.

It sounds like you need a TokenCache and a mechanism to inject it every time you do HttpClient.Send. This is what I propose you.

You should create a class called TokenCache, this class is responsible for getting the Token each time is expired, invalid or null.

public class TokenCache : ITokenCache
{
    public TokenClient TokenClient { get; set; }
    private readonly string _scope;
    private DateTime _tokenCreation;
    private TokenResponse _tokenResponse;

    public TokenCache(string scope)
    {
        _scope = scope;
    }

    private bool IsTokenValid()
    {
        return _tokenResponse != null && 
                !_tokenResponse.IsError &&
                !string.IsNullOrWhiteSpace(_tokenResponse.AccessToken) &&
                (_tokenCreation.AddSeconds(_tokenResponse.ExpiresIn) > DateTime.UtcNow);
    }

    private async Task RequestToken()
    {
        _tokenResponse = await TokenClient.RequestClientCredentialsAsync(_scope).ConfigureAwait(false);
        _tokenCreation = DateTime.UtcNow;
    }

    public async Task<string> GetAccessToken(bool forceRefresh = false)
    {
        if (!forceRefresh && IsTokenValid()) return _tokenResponse.AccessToken;

        await RequestToken().ConfigureAwait(false);

        if (!IsTokenValid())
        {
            throw new InvalidOperationException("An unexpected token validation error has occured during a token request.");
        }

        return _tokenResponse.AccessToken;
    }
}

You create a class TokenHttpHandler as shown below. This class is going to set the Bearer token each time you do a HttpClient.Send. Notice that we are using TokenCache (_tokenCache.GetAccessToken) to get the token inside SetAuthHeaderAndSendAsync method. This way you know for sure that your token is going to be sent each time you do calls from your api/mvc app to another api.

public class TokenHttpHandler : DelegatingHandler
{
    private readonly ITokenCache _tokenCache;

    public TokenHttpHandler(ITokenCache tokenCache)
    {
        InnerHandler = new HttpClientHandler();
        _tokenCache = tokenCache;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var response = await SetAuthHeaderAndSendAsync(request, cancellationToken, false).ConfigureAwait(false);

        //check for 401 and retry
        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            response = await SetAuthHeaderAndSendAsync(request, cancellationToken, true);
        }

        return response;
    }

    private async Task<HttpResponseMessage> SetAuthHeaderAndSendAsync(HttpRequestMessage request, CancellationToken cancellationToken, bool forceTokenRefresh)
    {
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await _tokenCache.GetAccessToken(forceTokenRefresh).ConfigureAwait(false));

        return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
    }

Then you use it inside the ExtendedHttpClient as shown below. Notice that we are Injecting the TokenHttpHandler into the constructor.

public class ExtendedHttpClient : HttpClient
{
    public ExtendedHttpClient(TokenHttpHandler messageHandler) : base(messageHandler)
    {
        DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
    }
}

And finally in your IoC configuration y you need to add the new classes.

If you want to reuse the above code for multiple MVC apps/Api, so you should put it in a shared library (for example infrastructure) and then only configure the IoC for each IdentityServer client.

builder.RegisterType<TokenHttpHandler>().AsSelf();
            builder.RegisterType<ExtendedHttpClient>().As<HttpClient>();

builder.RegisterType<TokenCache>()
                .As<ITokenCache>()
                .WithParameter("scope", "YOUR_SCOPES")
                .OnActivating(e => e.Instance.TokenClient = e.Context.Resolve<TokenClient>())
                .SingleInstance();

builder.Register(context =>
                {
                    var address = "YOUR_AUTHORITY";

                    return new TokenClient(address, "ClientID", "Secret");
                })
                .AsSelf();

Notice that this examples uses ClientCredentials flow but you can take this concept and modify it to make it fit with your requirements.

Hope it helps. Kind regards Daniel

like image 52
Daniel Botero Correa Avatar answered Oct 18 '22 04:10

Daniel Botero Correa