IdentityServer4 — Part 4 — Refresh Tokens — Digitteck
IdentityServer4 — Part 4 — Refresh Tokens
dotnet·20 October 2019·4 min read

IdentityServer4 — Part 4 — Refresh Tokens

Refresh tokens are credentials that grant an application the ability to obtain a new access token after the original expires — without requiring the user to re-authenticate.

  • Contain the information needed to obtain a new access_token or id_token
  • Subject to strict storage requirements — treat like a password
  • Typically do not expire on their own; revoke them if a security issue arises
  • Only issued for: Authorization Code and Authorization Code with PKCE
  • Require the offline_access scope in the initial authorization request

Refresh Token Request (HTTP)

The OpenID Connect specification defines the refresh flow as a POST to the token endpoint with grant_type=refresh_token:

javascript
POST /token HTTP/1.1
  Host: server.example.com
  Content-Type: application/x-www-form-urlencoded

  client_id=s6BhdRkqt3
    &client_secret=some_secret12345
    &grant_type=refresh_token
    &refresh_token=8xLOxBtZp8
    &scope=openid%20profile

Refresh Token Response

A successful response includes a new access token and optionally a rotated refresh token:

javascript
HTTP/1.1 200 OK
  Content-Type: application/json
  Cache-Control: no-store
  Pragma: no-cache

  {
   "access_token": "TlBN45jURg",
   "token_type": "Bearer",
   "refresh_token": "9yNOxJtZa5",
   "expires_in": 3600
  }

Token Response Model

csharp
// Token response model
public class TokenResponse
{
    public string AccessToken  { get; set; }
    public string RefreshToken { get; set; }
    public int    ExpiresIn    { get; set; }
    public string TokenType    { get; set; }
}

Exchange Authorization Code for Tokens

After the user authenticates and the authorization endpoint redirects with a code, exchange it for an access token and refresh token via the token endpoint:

csharp
// Exchange authorization code for access + refresh token (HttpClient, manual approach)
public async Task<TokenResponse> ExchangeCodeAsync(string authorizationCode)
{
    using var client = new HttpClient();

    var body = new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["grant_type"]    = "authorization_code",
        ["code"]          = authorizationCode,
        ["redirect_uri"]  = "https://myapp.com/callback",
        ["client_id"]     = "my_client",
        ["client_secret"] = "my_secret",
    });

    var response = await client.PostAsync("https://is4server.com/connect/token", body);
    var json     = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<TokenResponse>(json);
}

Exchange Refresh Token for New Access Token

When the access token expires, send the refresh token to the same token endpoint withgrant_type=refresh_token to receive a fresh access token without user interaction:

csharp
// Exchange refresh token for a new access token
public async Task<TokenResponse> RefreshTokenAsync(string refreshToken)
{
    using var client = new HttpClient();

    var body = new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["grant_type"]    = "refresh_token",
        ["refresh_token"] = refreshToken,
        ["client_id"]     = "my_client",
        ["client_secret"] = "my_secret",
    });

    var response = await client.PostAsync("https://is4server.com/connect/token", body);
    var json     = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<TokenResponse>(json);
}

Using IdentityModel Helpers

The IdentityModel NuGet package wraps the raw HttpClient calls. It discovers endpoint URLs from /.well-known/openid-configuration automatically:

csharp
// IdentityModel helpers — cleaner API over HttpClient
// NuGet: IdentityModel

// 1. Load the discovery document (populates endpoint URLs from /.well-known/openid-configuration)
using var httpClient = new HttpClient();

var disco = await httpClient.GetDiscoveryDocumentAsync("https://is4server.com");
if (disco.IsError) throw new Exception(disco.Error);

// 2. Exchange authorization code for tokens
var tokenResponse = await httpClient.RequestAuthorizationCodeTokenAsync(
    new AuthorizationCodeTokenRequest
    {
        Address      = disco.TokenEndpoint,
        ClientId     = "my_client",
        ClientSecret = "my_secret",
        Code         = authorizationCode,
        RedirectUri  = "https://myapp.com/callback",
    });

string accessToken  = tokenResponse.AccessToken;
string refreshToken = tokenResponse.RefreshToken;

// 3. Later: exchange the refresh token for a new access token
var refreshResponse = await httpClient.RequestRefreshTokenAsync(
    new RefreshTokenRequest
    {
        Address      = disco.TokenEndpoint,
        ClientId     = "my_client",
        ClientSecret = "my_secret",
        RefreshToken = refreshToken,
    });

string newAccessToken = refreshResponse.AccessToken;

Tags

.NETIdentityServer4OAuth2OpenID Connect
digitteck

© 2026 Digitteck