Search code examples
c#model-view-controlleridentityserver4

How to handle new refresh token on server side?


I have two projects. One is an Identity server 4 who handle users and authentication. The second need to use the first to login and ask for a token to access an API. When I need to refresh the token I don't now how to handle the new access token. How I can set to the authentification asp dot net core the new token. All the refresh process are made on a AuthorizationHandler.

I tried to modify the claims on identity but doesnt work. I tried to stock access token and refresh token inside my own cookie but I have trouble because when I refresh the token I can only use them at the next request (I didn't achieve to modify the request.cookies only the response.cookies.

public static async Task SetToken(TokenKind token,string value, HttpContext context)
{
    context.Response.Cookies.Append(GetTokenCookieName(token), value);
}


public static async Task<string> GetRefreshTokenAsync(HttpContext context)
{
    return await SearchToken(TokenKind.Refresh,context);
}


private static async Task<string> SearchToken(TokenKind token, HttpContext context)
{
    var tokenName = GetTokenName(token);
    var test = context.Request.Cookies;
    var apiToken = context.Request.Cookies.FirstOrDefault(x => x.Key == GetTokenCookieName(token)).Value;
    if (apiToken == null)
    {
        // Save token into cookie
        var tokenValue  = await context.GetTokenAsync(GetTokenName(TokenKind.Access));
        await SetToken(TokenKind.Access, tokenValue, context);

        var refreshTokenValue = await context.GetTokenAsync(GetTokenName(TokenKind.Refresh));
        await SetToken(TokenKind.Refresh, refreshTokenValue, context);

        switch (token)
        {
            case TokenKind.Access:
                return tokenValue;
            case TokenKind.Refresh:
                return refreshTokenValue;
            default:
                return null;
                break;
        }
    }
    else
    {
        return apiToken;
    }
}


private async Task<bool> RefreshToken(AuthorizationFilterContext mvcContext, HttpClient client)
{
    var refreshToken = await TokenUtils.GetRefreshTokenAsync(mvcContext.HttpContext);
    //await mvcContext.HttpContext.GetTokenAsync("refresh_token");

    var variables = new Dictionary<string, string>
            {
                { "grant_type", "refresh_token" },
                { "client_id", _configuration["ApplicationOptions:ClientId"] },
                { "client_secret", _configuration["ApplicationOptions:ClientSecret"] },
                { "refresh_token", refreshToken }
            };
    var content = new FormUrlEncodedContent(variables);

    var url = _configuration["ApplicationOptions:AuthorizeServer"] + "/connect/token";
    var response = await client.PostAsync(url, content);

    if (response.IsSuccessStatusCode == false)
    {
        var errorString = await response.Content.ReadAsStringAsync();
        var errorData = JsonConvert.DeserializeObject<dynamic>(errorString);
        return false;
    }

    var contentAsString = await response.Content.ReadAsStringAsync();
    var responseData = JsonConvert.DeserializeObject<dynamic>(contentAsString);
    var newAccessToken = (string)responseData.access_token;
    var newRefreshToken = (string)responseData.refresh_token;

    await TokenUtils.SetAccessToken(newAccessToken, mvcContext.HttpContext);
    await TokenUtils.SetRefreshToken(newRefreshToken, mvcContext.HttpContext);

    var result = await mvcContext.HttpContext.AuthenticateAsync();
    if (result.Succeeded)
    {
        result.Properties.StoreTokens(new List<AuthenticationToken>
                {
                    new AuthenticationToken
                    {
                        Name = OpenIdConnectParameterNames.AccessToken,
                        Value = newAccessToken
                    },
                    new AuthenticationToken
                    {
                        Name = OpenIdConnectParameterNames.RefreshToken,
                        Value = newRefreshToken
                    }
                });
        return true;
    }
    else
    {
        return false;
    }
}

I would like to know what is the good pratice to store (or replace the actual access token) with the .net core authentification. I'm sure I'm not doing it the right way. At the end I want to handle correctly my token and my refresh token for not ask the user to login again. Now with my solution my project will denied the access when it need to refresh the token and next request will be granted (because the cookie need to be resend from the user).


Solution

  • Thank to Ruard van Elburg I found the solution (here's the complete answer) And that's what I used to replace my tokens:

    // Save the information in the cookie
    var info = await mvcContext.HttpContext.AuthenticateAsync("Cookies");
    
    info.Properties.UpdateTokenValue("refresh_token", newRefreshToken);
    info.Properties.UpdateTokenValue("access_token", newAccessToken);
    info.Properties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
    
    await mvcContext.HttpContext.SignInAsync("Cookies", info.Principal, info.Properties);