5

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??

Jon H
  • 1,061
  • 4
  • 13
  • 32

4 Answers4

7

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

  • Thank you so much but I don't see how this helps with reusing? How does this extract/expose the token used for hope 1a->1b to it can be used for 1b->2a/2b/c and then again 2x->y. – Jon H Sep 20 '17 at 11:57
  • I am sorry, I forgot to say in my answer that a token should be issued for one client and used by only that client. I am actually showing you one way to issue tokens and cache them so you don't have to worry about them. I am going to edit my answer. – Daniel Botero Correa Sep 20 '17 at 14:10
  • @DanielBoteroCorrea I tried to implement that using Autofac as IoC container, and unfortunately everytime the code hits: return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); I've got either The Task was canceled or The object was disposed ... :/ – John Jun 26 '19 at 13:32
3

This is the very common problem for Micro services architecture and it is handled through API gateway pattern. All the token validation should be handled at the API gateway level. After token validation, the request should be forwarded to a (micro)service, that service can trust the request. If you have anything to update/fix/improve/add regarding token security, it's done in a single place.

PAVITRA
  • 761
  • 2
  • 12
  • 24
ManishSingh
  • 1,804
  • 1
  • 12
  • 11
  • I have thought and thought and thought on this.. and I am rapidly coming to the conclusion that this is the right approach. This is in the absence of a rigorous debate or link to best practice that I was hoping for. It seems to make sense to me that I should only have to ask for permission to access service ONE. my credentials shouldn't then impact on that services ability to access subsequent calls for it to perform its function. – Jon H Sep 26 '17 at 07:02
  • I guess the main take-away thing from this - and this is what stirs the debate is that it IS NOT correct for services that are consumed by other services to use tokens between them - only when exposed publicaly is token authentication appropriate. – Jon H Sep 26 '17 at 07:03
3

Poor Man's Delegation - simply forward the same bearer token in the subsequent API calls. As stated in other comments, this introduces discrepancies in scope.

Extension Grants - Identity Server 4 introduces this grant type to support delegation. Provide the bearer token in exchange for a new token to call the second API.

wonster
  • 746
  • 1
  • 9
  • 11
0

I think you are right, you simply pass along the token. Obviously the token needs the scopes for all api's it might hit. 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...

Matt G
  • 51
  • 7
  • That's a bad idea in my opinion. – Daniel Botero Correa Sep 20 '17 at 14:19
  • "may lead to security issues" - could you elaborate? "the scopes of all the Apis" - yes but it would have the scopes for the first level of api's anyway, so why not the second level? The token is containing authentication information only. It would not be ok for the token to be providing authorisation information as that may well be client/api specific. – Matt G Sep 20 '17 at 14:32
  • e.g. Giving access to an scope in Api1 which may mean something different in Api2, so you can actually execute code that you are not allowed to. – Daniel Botero Correa Sep 20 '17 at 14:41
  • I guess that's what I mean about authorisation. I see the token as authenticating you only, not giving authorisation. What the bearer of the token is allowed to do should be for each api to decide upon. The token identifies the bearer only, not what they can do. – Matt G Sep 20 '17 at 14:48
  • Or even worse, if someone steal your token, he has access to all your Apis. I think this solution has too many disadvantages. Do you guys have a good reason to do Matt G, Jon H? – Daniel Botero Correa Sep 20 '17 at 14:50
  • You are giving authorization based on scopes in this case at the endpoint. Those scopes come from the token. – Daniel Botero Correa Sep 20 '17 at 14:58
  • I'm giving access to an api with a scope, not the ability to do anything. The api should make it's own calls to determine what the bearer can do. I wouldn't want to manage and store multiple tokens for each api I need to access. The stealing token issue is always an issue and is only partially mitigated by different tokens. Keep lifetimes short. – Matt G Sep 20 '17 at 14:58
  • Api1 will issue one token to call all its apis. But Api2 cannot use that same token. That's why you have to issue a token for each client and that's Jon H's question – Daniel Botero Correa Sep 20 '17 at 15:02
  • Food for thought :) – Matt G Sep 20 '17 at 15:11
  • Yes, what you are saying is right. Api1 ask for a token to the IdP give it back an access token with scopes. the scopes allow it to get access all the apis it asked for. If the Apis are giving the authorization to execute code for example based on your id_token, or your windows roles or your age etc at the endpoint probably sharing the token between all the Apis is a possible idea (as long as the authorization is not scope-based (some people do that)). Now, if Api2 uses the same token it will have access to scopes that it may not need. That's something that annoys me. Nice conversation. ;) – Daniel Botero Correa Sep 20 '17 at 15:35
  • 1
    I agree with you. If authorisation information is in the token, then don't share between apis, if it's not then it should be ok to share. At the moment I think a lot of people include authorisation in the token. This talk was very informative: https://www.youtube.com/watch?v=EJeZ3YNnqz8 Thanks, nice chat :) – Matt G Sep 20 '17 at 15:59
  • Oh yes, I already watched that video. It's really good. Dominick Baier & Brock Allen are the experts. – Daniel Botero Correa Sep 20 '17 at 16:14
  • Hi guys - thanks for the discussion, I've added some comments to ManishSingh answer as this feels more 'correct'. I'd love for this to stir the debate some more - but basically i have concluded that tokens are for the first stop in public api access.. intra-service use requires something more mundane like ip/subnet or un/pw me thinks.... discuss. – Jon H Sep 26 '17 at 07:06
  • Please see here: http://microservices.io/patterns/security/access-token.html The problem with doing all the token validation at the gateway is that your gateway needs knowledge. The decision on what a user is allowed to do should be made as close to the thing being considered as possible. How would a gateway know what information you are allowed access to? That decision is for the microservice to make in my opinion. If there is no authorisation to perform then sure you don't need to pass a token down. – Matt G Sep 26 '17 at 14:01