Still logged in MVC site, but can’t call web API

There are two types of authentication, cookie and bearer.

Where the cookie keeps you logged in, the bearer token can’t. Because the bearer token is set to expire at some point, without allowing you to change the lifetime.

The only way to access the resource (api) after the access token expires is to either let the user login again or request a new access token using a refresh token, without needing user interaction.

You’ve already configured it:

options.Scope.Add("offline_access");

On each login the request will at least contain a refresh token. Store it at a safe place and use it when needed. By default it is set to one time use only.


You can use something like this code to renew the token (as you are not actually refreshing it, but rather replacing it). You’ll need to include the ‘IdentityModel’ NuGet package, as seen in the samples from IdentityServer.

private async Task<TokenResponse> RenewTokensAsync()
{
    // Initialize the token endpoint:
    var client = _httpClientFactory.CreateClient();
    var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");

    if (disco.IsError) throw new Exception(disco.Error);

    // Read the stored refresh token:
    var rt = await HttpContext.GetTokenAsync("refresh_token");
    var tokenClient = _httpClientFactory.CreateClient();

    // Request a new access token:
    var tokenResult = await tokenClient.RequestRefreshTokenAsync(new RefreshTokenRequest
    {
        Address = disco.TokenEndpoint,

        ClientId = "mvc",
        ClientSecret = "secret",
        RefreshToken = rt
    });

    if (!tokenResult.IsError)
    {
        var old_id_token = await HttpContext.GetTokenAsync("id_token");
        var new_access_token = tokenResult.AccessToken;
        var new_refresh_token = tokenResult.RefreshToken;
        var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);

        // Save the information in the cookie
        var info = await HttpContext.AuthenticateAsync("Cookies");

        info.Properties.UpdateTokenValue("refresh_token", new_refresh_token);
        info.Properties.UpdateTokenValue("access_token", new_access_token);
        info.Properties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));

        await HttpContext.SignInAsync("Cookies", info.Principal, info.Properties);
        return tokenResult;
    }
    return null;
}

By default the refresh token usage is configured as one time use. Please note that when storing the new refresh token fails and you should lose it, then the only way to request a new refresh token is to force the user to login again.

Also note that the refresh token can expire.


And taking it one step back, you’ll need to use this when the access token expired or is about to expire:

var accessToken = await HttpContext.GetTokenAsync("access_token");

var tokenHandler = new JwtSecurityTokenHandler();

var jwtSecurityToken = tokenHandler.ReadJwtToken(accessToken);

// Depending on the lifetime of the access token.
// This is just an example. An access token may be valid
// for less than one minute.
if (jwtSecurityToken.ValidTo < DateTime.UtcNow.AddMinutes(5))
{
    var responseToken = await RenewTokensAsync();
    if (responseToken == null)
    {
        throw new Exception("Error");
    }
    accessToken = responseToken.AccessToken;
}

// Proceed, accessToken contains a valid token.

Leave a Comment