In this article, we are going to explore and implement the usage of a refresh token in our Blazor WebAssembly.In Part 1 we have explored complete guidance for implementing authentication in the Blazor WebAssembly application from scratch.
Now let's try to access Todos API after the expiration of the access token.The token expiration issue can be resolve by using the refresh token.
Services/TokenManagerService:
Store Refresh Token In-Browser LocalStorage:
If we recall Part1, we have stored our access token in the browser localStorage, similarly, we need to store our refresh token in the browser localStorage. Now update the 'LoginAsync' method in the AccountService file to save the refresh token.
Services/AccountService:
public async Task<bool> LoginAsync(LoginModel model) { var response = await _httpClient.PostAsJsonAsync<LoginModel>("/account/login-token", model); if (!response.IsSuccessStatusCode) { return false; } AuthResponse authData = await response.Content.ReadFromJsonAsync<AuthResponse>(); await _localStorageService.SetItemAsync("token", authData.Token); await _localStorageService.SetItemAsync("refreshToken", authData.RefreshToken); (_customAuthenticationProvider as CustomAuthenticationProvider).Notify(); return true; }
- (Line: 10) Storing refresh token to browser localStorage.
Try To Access Secured API:
Now we have the access token, now let's try to consume a secured API endpoint from our Blazor WebAssembly application. I have created a test todo endpoint in Jwt Authentication Project(Git link), I will try to consume the 'todo' endpoint from our blazor web application.
Now let's create 'TodoService' files to invoke the API Call.
Services/ITodoService.cs:
using System.Collections.Generic; using System.Threading.Tasks; namespace BlazorWasm.JwtAuthLearning.Services { public interface ITodoService { Task<List<string>> GetTodos(); } }Services/TodoService.cs:
using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Threading.Tasks; namespace BlazorWasm.JwtAuthLearning.Services { public class TodoService:ITodoService { private readonly HttpClient _httpClient; public TodoService(HttpClient httpClient) { _httpClient = httpClient; } public async Task<List<string>> GetTodos() { return await _httpClient.GetFromJsonAsync<List<string>>("/test/todos"); } } }
- (Line: 12) Injected HttpClient.
- (Line: 19) Invoking 'Todo' endpoint.
Pages/Index.razor:(Update Authorized Component Part)
<Authorized> <h1>Your Dashboard - Your Claims</h1> <div> <button type="button" class="btn btn-primary" @onclick="Logout">Logout</button> </div> <div> @foreach (var claim in userClaim) { <div>@claim.Type - @claim.Value</div> } </div> <br/> <div> @if (todos.Count == 0) { <div> <button type="button" class="btn btn-primary" @onclick="GetTodos">See Your Todos</button> </div> } <ul> @foreach (var item in todos) { <li>@item</li> } </ul> </div> </Authorized>
- (Line: 17) Button to invoke the 'Todo' endpoints.
- (Line: 21-24) Loop and bind the 'Todo' collection.
Pages/Index.razor:
@inject ITodoService _todoService;Pages/Index.razor:(Code Part To Invoke Todo API)
@code{ private List<string> todos = new List<string>(); private async Task GetTodos() { todos = await _todoService.GetTodos(); } // code hidden for display purpose }Now after login and click on the show todos button will invoke the 'Todo' endpoint which will return the response of UnAuthorized user as below.The reason behind this unauthorized response, we missed sending the access token as an authorize header to the API call.
Create TokenManagerService:
Now we will create TokenManagerService which provides token all over to the application. In this token manager, we implement steps like fetching token, validating token(expiration), calling refresh token endpoint, etc.
Services/ITokenManagerService.cs:
using System.Threading.Tasks; namespace BlazorWasm.JwtAuthLearning.Services { public interface ITokenManagerService { Task<string> GetTokenAsync(); } }Services/TokenManagerService.cs:
using Blazored.LocalStorage; using BlazorWasm.JwtAuthLearning.Helpers; using BlazorWasm.JwtAuthLearning.Models; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Json; using System.Security.Claims; using System.Threading.Tasks; namespace BlazorWasm.JwtAuthLearning.Services { public class TokenManagerService: ITokenManagerService { private readonly ILocalStorageService _localStorageService; public TokenManagerService(ILocalStorageService localStorageService) { _localStorageService = localStorageService; } public async Task<string> GetTokenAsync() { string token =await _localStorageService.GetItemAsync<string>("token"); return token; } } }
- (Line: 17) Injected ILocalStorageService.
- (Line: 24) Fetching access token from the browser's local storage.
Services/TodoService.cs:
private readonly HttpClient _httpClient; private readonly ITokenManagerService _tokenManagerService; public TodoService(HttpClient httpClient, ITokenManagerService tokenManagerService) { _httpClient = httpClient; _tokenManagerService = tokenManagerService; } public async Task<List<string>> GetTodos() { string token = await _tokenManagerService.GetTokenAsync(); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token); return await _httpClient.GetFromJsonAsync<List<string>>("/test/todos"); }
- (Line: 5) Injected ITokenManagerService
- (Line: 13) Fetching access token using ITokenManagerService.
- (Line: 14) Adding token as authorize header to the HttpClient instance.
Now let's try to access Todos API after the expiration of the access token.The token expiration issue can be resolve by using the refresh token.
Integrate Refresh Token Endpoint:
Now the first thing we need to check whether our access token stored in browser local storage is expired or not. This expiration can be verified by using a claim called 'exp'. So let's create a private method in TokenManagerService to validate the access token expiration as below.
Services/TokenManager.cs:
private bool ValidateTokenExpiration(string token) { List<Claim> claims = JwtParser.ParseClaimsFromJwt(token).ToList(); if(claims?.Count == 0) { return false; } string expirationSeconds = claims.Where(_ => _.Type.ToLower() == "exp").Select(_ => _.Value).FirstOrDefault(); if (string.IsNullOrEmpty(expirationSeconds)) { return false; } var exprationDate = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(expirationSeconds)); if(exprationDate < DateTime.UtcNow) { return false; } return true; }
- (Line: 1) Passing our access token as an input parameter.
- (Line: 3) Fetching claims from access token using JwtParser helper class.
- (Line: 8) Fetching expiration seconds from the 'exp' claim.
- (Line: 14) Converting expiration seconds to date time.
Services/TokenManagerService:
private readonly HttpClient _httpClient; private readonly ILocalStorageService _localStorageService; public TokenManagerService(HttpClient httpClient, ILocalStorageService localStorageService) { _httpClient = httpClient; _localStorageService = localStorageService; }Now let's create a private method that will invoke the refresh token endpoint.
Models/TokenModel.cs:(Refresh Token Payload Type)
namespace BlazorWasm.JwtAuthLearning.Models { public class TokenModel { public string Token { get; set; } public string RefreshToken { get; set; } } }Services/TokenManagerService:(Method to call refresh token endpoint)
private async Task<string> RefreshTokenEndPoint(TokenModel tokenModel) { var response = await _httpClient.PostAsJsonAsync<TokenModel>("/account/activate-token-by-refreshtoken", tokenModel); if (!response.IsSuccessStatusCode) { return string.Empty; } AuthResponse authResponse = await response.Content.ReadFromJsonAsync<AuthResponse>(); await _localStorageService.SetItemAsync<string>("token", authResponse.Token); await _localStorageService.SetItemAsync<string>("refreshToken", authResponse.RefreshToken); return authResponse.Token; }
- (Line: 3) Invoking refresh token endpoint.
- (Line: 8) Reading the response data.
- (Line: 9-10) Storing both new access token and refresh token to browser local storage.
Services/TokenManagerService:
public async Task<string> GetTokenAsync() { string token =await _localStorageService.GetItemAsync<string>("token"); if (string.IsNullOrEmpty(token)) { return string.Empty; } if (ValidateTokenExpiration(token)) { return token; } string refreshToken = await _localStorageService.GetItemAsync<string>("refreshToken"); if (string.IsNullOrEmpty(refreshToken)) { return string.Empty; } TokenModel tokenModel = new TokenModel { Token = token, RefreshToken = refreshToken }; return await RefreshTokenEndPoint(tokenModel); }
- (Line: 9) Validating access token expiration.
- (Line: 20) Preparing payload object for refresh token endpoint.
- (Line: 21) Invoking refresh token endpoint
That's all about the refresh token integration in blazor webassembly authentication.
Support Me!
Buy Me A Coffee
PayPal Me
Wrapping Up:
Hopefully, I think this article delivered some useful information on the refresh token integration for authentication implementation from scratch in the Blazor WebAssembly application. I love to have your feedback, suggestions, and better techniques in the comment section below.
Comments
Post a Comment