How do I get the access token from a blazor (server-side) web app?
After implementing openidconnect, where does blazor store the access token? how to retrieve it?
How to add OpenIdConnect via IdentityServer4 to ASP.NET Core ServerSide Blazor web app?
https://docs.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1&tabs=visual-studio#customize-unauthorized-content-with-the-router-component
The following code snippets provide a way to retrieve the access token issued when a user is authenticated with IdentityServer4 provider. In order to get the access token you can use the HttpContext object, but since Blazor is SignalR-based, you'll have to do it the only time the HttpContext object is available, when the connection to your application is an HTTP connection, and not a WebSocket connection.
After retrieving the access token, you need to pass it to your Blazor app, and store it in a local storage. My code also provide a way to parse the access token, if necessary.
Add a file to the Pages folder and name it _Host.cshtml.cs
-
Add this code to the file:
public class HostAuthenticationModel : PageModel { public async Task<IActionResult> OnGet() { if (User.Identity.IsAuthenticated) { var token = await HttpContext.GetTokenAsync("access_token"); AccessToken = token; } return Page(); } public string AccessToken { get; set; } }
Note: I've name the the PageModel class: HostAuthenticationModel
You'll need some of these:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
using System.Linq;
using System.Threading.Tasks;
- Next we have to pass the value stored in the AccessToken property to the Blazor App:
In the _Host.cshtml file add the model directive at the top portion of the file:
@model HostAuthenticationModel
Add a new attribute to the component Tag Helper like this:
param-AccessToken="Model.AccessToken"
Final result:
<app>
<component type="typeof(App)" render-mode="ServerPrerendered"
param-AccessToken="Model.AccessToken"/>
</app>
The param-AccessToken
attribute requires you to define a property named AccessToken in the App component which will get the access token from the page model.
- Next define the property which will receive the access token
-
And then override the OnAfterRenderAsync method from which we call a method to store the access token in the local storage.
@code{ [Parameter] public string AccessToken { get; set; } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await tokenStorage.SetTokenAsync(AccessToken); } } }
Also place the following at the top of the App component:
@inject AccessTokenStorage tokenStorage
-
Next you'll have to create the AccessTokenStorage service like this:
Create a class named AccessTokenStorage at the root of your app, and add the following code:
public class AccessTokenStorage { private readonly IJSRuntime _jsRuntime;
public AccessTokenStorage(IJSRuntime jsRuntime) { _jsRuntime = jsRuntime; } public async Task<string> GetTokenAsync() => await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "accessToken"); public async Task SetTokenAsync(string token) { if (token == null) { await _jsRuntime.InvokeAsync<object>("localStorage.removeItem", "accessToken"); } else { await _jsRuntime.InvokeAsync<object>("localStorage.setItem", "accessToken", token); } } }
I guess no explanation is needed here... Here's some using directives you may need
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.JSInterop;
Add the following to the Startup.ConfigureServices
services.AddHttpClient();
services.AddScoped<AccessTokenStorage>();
Note: the above code should be used with the code I provide in my answer here
I resolved this issue by adding the code listed in the link below.
https://docs.microsoft.com/en-us/powerapps/developer/data-platform/webapi/quick-start-blazor-server-app#prepare-the-app-to-use-azure-ad-tokens
Note: The steps listed in the link above work fine, however, I made some small modifications that make more sense to me. I also modified the order that make more sense to me.
Steps:
1. Create the TokenProvider class
public class TokenProvider
{
public string AccessToken { get; set; }
}
2. Update the _Host.cshtml file with the following:
@using Microsoft.AspNetCore.Authentication
@{
var accessToken = await HttpContext.GetTokenAsync("access_token");
}
<body>
<app>
<component type="typeof(App)" param-AccessToken="accessToken" render-mode="ServerPrerendered" />
</app>
3. Update StartUp.cs with DI:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddScoped<TokenProvider>();
4. Update App.razor with the following:
@inject TokenProvider TokenProvider
@code
{
[Parameter]
public string AccessToken { get; set; }
protected override void OnInitialized()
{
//Accept the parameter from _Host.cshtml and move into the Token Provider
TokenProvider.AccessToken = AccessToken;
base.OnInitialized();
}
}
5. Create an instance of the _tokenProvider in the constructor and use it to get the Access Token
Note: Below I get the access token NOT in the @code block or the code behind of the Blazor page, but I am using a Service class. (The page calls the Service class). I hope this makes sense. Again, I would suggest reviewing the link above.
private readonly TokenProvider _tokenProvider;
//Create tokenProvider using constructor Dependency Injection
public HttpClientUtility(TokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
var accessToken = _tokenProvider.AccessToken;
if (accessToken != null)
{
_httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
}
Hopefully the above steps will help someone else.