IdentityServer 3 refresh user with refresh token
Asked Answered
C

2

11

We are trying to set up Identity Server 3 in the right way. We got authentication working fine and we manage to retrieve the refresh token.

The client application is using Angular.

Now when the acces_token expires any calls to the rest api fails (we managed to get it to return 401) but we are wondering how to re-authenticate the user.

In our tests, any api call made from Javascript is failing (401) but as soon as the page is refreshed the whole mechanism is kicking in. We do see that we are redirected to the identity server but it does not show up the login page, we are sent back to the client application with new tokens apparently.

What I would like to do is to refresh the access token without having to force the user to refresh the page.

What I'm not sure though is whose responsibility is it? Is that the client application (website) or the angular application? In other word, should the application handle this transparently for Angular or should angular do something when it receives a 401, in which case, I'm not too sure how the information will flow back to the web app.

Any clue?

Additional Information: We are using OpenId Connect

Compotation answered 10/12, 2015 at 13:23 Comment(8)
I have the same problem, I guess the Controller displaying the application and the Controller(s) called by the client (ajax) should use different type of Authentication, so app.UseOpenIdConnectAuthentication() only for the first type, and for the second... don't know but I'm trying to understandManchineel
I'm starting to think that I should actually push the access_token and refresh_token down to the js application but that's a tiny bit nasty IMHO. Trying to find a good way to do that. I actually looked at how MS is doing it in the Azure Portal and that is definitely nasty...Compotation
The correct "second type" of authentication needed should be something with bearer token, so app.UseOpenIdConnectAuthentication for MVC views and app.UseIdentityServerBearerTokenAuthentication (or another bearer) for API. Meanwhile my workaround is extending Tokens live in Client configurations on Identity server --- AccessTokenLifetime = 3600 * 12, // extend from default 3600 sec / 1 hour to 12 hours --- IdentityTokenLifetime = 300 * 20 * 12, // extend from default 300 sec / 5 min to 12 hoursManchineel
Don't know what kind of client you have but in my case I can use this jeremymarc.github.io/2014/08/14/… so basically intercepting the rejected calls and refreshing the token. In the meantime, queuing the new requests until we have a refreshed token.Compotation
And indeed, I actually use the app.UseIdentityServerBearerTokenAuthenticationCompotation
nice example, I can adapt my application to do something similar. In fact with Bearer Authentication enabled I get a 401 when bearer token is expired, but... how do you refresh the token using angularJS? I see in page code some hidden input with name access_token, id_token and so on. But how to use them to actually refresh?Manchineel
I'm currently implementing that part. Just validated that the library referenced in the article is working fine and removing my custom code that was redirecting to login page. I believe a simple http post to IdentityServer with proper parameters should do. I'll confirm later on today.Compotation
I am in the process of answering my own question in order to fully explain how I got that whole thing working. I hope it will help.Compotation
C
4

I got it working!

As I said in the comments I used this article. The writer is referencing a very nice lib that I am using as well.

Facts:

  1. Identity Server 3 is requesting the client secret upon access token refresh
  2. One should not store the refresh_token or the client_secret on the javascript application as they are considered unsafe (see the article)

So I chose to send the refresh_token as en encrypted cookie sith this class (found of ST BTW, just can't find the link anymore, sorry...)

public static class StringEncryptor
{
    public static string Encrypt(string plaintextValue)
    {
        var plaintextBytes = plaintextValue.Select(c => (byte) c).ToArray();
        var encryptedBytes = MachineKey.Protect(plaintextBytes);
        return Convert.ToBase64String(encryptedBytes);
    }

    public static string Decrypt(string encryptedValue)
    {
        try
        {
            var encryptedBytes = Convert.FromBase64String(encryptedValue);
            var decryptedBytes = MachineKey.Unprotect(encryptedBytes);
            return new string(decryptedBytes.Select(b => (char)b).ToArray());
        }
        catch
        {
            return null;
        }
    }
}

The javascript application is getting the value from the cookie. It then deletes the cookie to avoid that thing to be sent over and over again, it is pointless.

When the access_token becomes invalid, I send an http request to the application server with the encrypted refresh_token. That is an anonymous call.

The server contacts the identity server and gets a new access_token that is sent back to Javascript. The awesome library queued all other requests so when I'm back with my new token, I can tell it to continue with authService.loginConfirmed();.

The refresh is actually pretty easy as all you have to do is to use the TokenClient from IdentityServer3. Full method code:

    [HttpPost]
    [AllowAnonymous]
    public async Task<JsonResult> RefreshToken(string refreshToken)
    {
        var tokenClient = new TokenClient(IdentityServerConstants.IdentityServerUrl + "/connect/token", "my-application-id", "my-application-secret");
        var response = await tokenClient.RequestRefreshTokenAsync(StringEncryptor.Decrypt(refreshToken));

        return Json(new {response.AccessToken});
    }

Comments are welcome, this is probably the best way to do that.

Compotation answered 15/12, 2015 at 16:15 Comment(10)
Nice but there is a thing I don't understand. How do you effectively renew your credential once you get the response.AccessToken? Basically I call RefreshToken function when I get a response.status == 401 from one of my ajax call and I get the new AccessToken to the client... then what?Manchineel
I thought I could renew credential directly in RefreshToken Controller function but there I'm an anonymous user. Is there any kind of code to re-authenticate immediately, something like Request.GetOwinContext().Authentication.UnknowFunctionToRenewMyAuth(response.AccessToken)Manchineel
Not sure I understand the question... the RequestRefreshTokenAsync method is only there to renew the access token. What are you trying to achieve with Credentials?Compotation
I have 2 controllers in the same project, one providing HTML code (almost reduced to an AngularJS SPA) and one providing JSON. I authenticate the first with app.UseOpenIdConnectAuthentication and the latter with appRest.UseIdentityServerBearerTokenAuthentication. I guess there is a method to actually refresh the token validity without manually handling with JS the bearer header of each ajax request to API Controller, am I wrong?Manchineel
That's the thing actually, you do not refresh the validity of a token, you just get a new one. I think the API should (as you did) use only the bearer token, which means that JS has to deliver it to the controller in the header every time. However, you can perfectly use an interceptor in Angular to do that cause it would just be a pain to do it on each http calls.Compotation
Is there a specific piece of code from IdentityServer3.Samples repo that helped you implement your solution? Are you willing to share what you have implemented so far to manage bearer header?Manchineel
Well, this is really some custom implementation. I suggest you create a question specifically for this, it'll be easier to reply (easier on formatting)Compotation
You're right, thank you for your support, at least now I know what I have to doManchineel
If you encrypt the refresh token and then decrypt it in your refresh endpoint, how is this any more secure than just sending down the refresh token as is? If someone were to gain access to the encrypted refresh token, all they'd have to do is send it to your endpoint that decrypts it. Problem solved. This is no more secure than just sending the refresh token as is. If you're worried about exposing the refresh token to the client application, you should just persist it server side and use some kind of a key (auth token, user id, etc.) to retrieve it server side and then send it to the endpoint.Vaasa
For reference, we completely changed the way it worked. Now the AngularJS application is using a cookie for authentication with it's server app and the server app keeps the tokens in the users claims and manages the token lifetime. It then uses the tokens to communicate with the API application.Compotation
C
3

For future reference - using refresh tokens in an angular (or other JS) application is not the correct way as a refresh token is too sensitive to store in the browser. You should use silent renew based on the identityserver cookie to get a new access token. Also see the oidc-client-js javascript library, as this can manage silent renew for you.

Cartouche answered 30/5, 2017 at 9:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.