11
votes

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

2
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 understandNaigel
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...Georges Legros
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 hoursNaigel
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.Georges Legros
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.Georges Legros

2 Answers

4
votes

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.

3
votes

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.