Handle Token Refresh

You can use refresh tokens to get new access tokens when they expire.

After a user successfully authenticates, Wristband will redirect to your application's Callback Endpoint. As part of the Callback Endpoint, a call is made to Wristband's Token Endpoint to get the access and refresh token for the user. We haven't used these tokens in this quickstart guide since we're relying entirely on the user's session cookie to authenticate requests. However, depending on your architecture, there may be situations where you'll need to utilize the user's access token. Listed below are some common examples of when you'd want to use the access token:

  1. Identity Federation - If your server needs to make API calls to other downstream services (for example, if you have a microservice architecture), then the access token can be used as a bearer token to authenticate requests to those downstream services. Typically, the access token will be placed in the Authorization header of the outbound request. When the downstream service receives the request, it can validate the access token and derive the authenticated user's claims from the token.
  2. Providing Access Tokens To Frontend Clients - In some cases, frontend code running in the browser may need to use the access token to authenticate with backend APIs rather than the session cookie. For example, WebSockets tend to use an access token for authentication rather than cookies. Also, there may be some APIs that the frontend needs to call that don't support cookie authentication but instead require an access token. In cases where the frontend does need to utilize access tokens, we don't recommend storing the access token in the browser since that makes them vulnerable to XSS attacks. Instead, we'd recommend using the Token-Mediating Backend, which stores the tokens on the backend but allows the frontend to retrieve the access token from the backend.
  3. Session Revocation - In this quickstart guide, we've been using stateless sessions, where all the session information is stored within the session cookie. This approach is nice because it's simple to implement and doesn't require any server-side storage for storing session data. However, one downside to this approach is that it's not possible for an outside party to immediately revoke the user's session since it's stored within the user's browser. One workaround to this limitation is to create a short-lived access token (for example, have the token expire after 5 minutes) and then couple the session's validity with the access token's validity (i.e., if the access token expires, then the session should become invalid). Under normal circumstances, the refresh token can be used to retrieve new access tokens to keep the session valid; however, we can manually revoke the refresh token to make the user's session invalid once their current access token expires. While the session invalidation is not immediate, it should take effect within a small amount of time, assuming the access token expiration time is short.

If you decide to utilize the access token within your application, you'll need to update your auth middleware to refresh the token. Otherwise, you can skip this section.

Update Auth Middleware to Refresh Tokens

You can use the Wristband SDK RefreshTokenIfExpired() function to update your auth middleware to handle refreshing expired access tokens.

// AuthMiddleware.cs

public class AuthMiddleware
{
    private readonly RequestDelegate _next;
    public AuthMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context, IWristbandAuthService wristbandAuth)
    {
        if (context.GetEndpoint()?.Metadata.GetMetadata<RequireWristbandAuth>() == null)
        {
            await _next(context);
            return;
        }

        if (!await IsAuthenticated(context))
        {
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return;
        }
      
        /* ***** BEGIN TOKEN REFRESH LOGIC ***** */

        try
        {
            var refreshToken = context.User.FindFirst("refreshToken")?.Value ?? string.Empty;
            var expiresAt = long.TryParse(context.User.FindFirst("expiresAt")?.Value, out var exp) ? exp : 0;
            var tokenData = await wristbandAuth.RefreshTokenIfExpired(refreshToken, expiresAt);

            // Update claims with the new token data, if the access token was refreshed.
            var claims = context.User.Claims;    
            if (tokenData != null)
            {
                claims = claims
                    .Where(c => !new[] { "accessToken", "refreshToken", "expiresAt" }.Contains(c.Type))
                    .Concat(new[]
                    {
                        new Claim("accessToken", tokenData.AccessToken),
                        new Claim("refreshToken", tokenData.RefreshToken ?? string.Empty),
                        new Claim("expiresAt", $"{DateTimeOffset.Now.ToUnixTimeMilliseconds() + (tokenData.ExpiresIn * 1000)}")
                    });
            }

            await context.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                    new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme)),
                new AuthenticationProperties { IsPersistent = true });
            await _next(context);
        }
        catch (Exception ex)
        {
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        }
      
        /* ***** END TOKEN REFRESH LOGIC ***** */
    }

    ...
}

What’s Next

Let's make sure the token refresh logic is working.