The title ”Call API Management from Blazor” is not explain fully what I’m going to explain in this post but it is only a title. So, consider the following scenario.
Scenario
On Azure API Management Service you have your APIs. For more protection, you want to add another level of security asking to the API Management to validate the user token for each request. The token is validated again your own Identity Server.
Once the API Management is configured to use Identity Server for the validation, you want to call the APIs from a Blazor WebAssembly application. So, the first issue you will face is how to read the user token with Blazor and then add the Authorization
to the HttpClient
request.
Now, I must confess I spent almost 2 weeks to find a solution to all of this. I hope this post could be useful for someone else.
Before starting to read this post, I recommend to read my other posts about the API Management Service:
Configure OpenID Connect
So, first step is to configure the OpenID connect on the API Management Service on Azure. For that, go to the resource and on the menu on the left, select OAuth 2.0 + OpenID Connect.

Then, at the top click on Add to add a new configuration.Now, type the Display name and the Name you want to use, the Description and the Metadata endpoint URL. If you are using the Identity Server and the Skoruba Admin UI, click on Discovery Document to obtain the URL.

Then, Client ID and Client Secret are required. Again, you can use Skoruba Admin UI to create them. The configuration is quite simple:
- Require Client Secret
- Allow Offline Access
- Allow Access Token Via Browser
- Allowed scopes:
- openid
- profile
- roles
- Allowed Grant types
- client_credentials
- implicit
- authorization_code
- password
- hybrid
Then, set a Client Secret. If you use the code to configure the IdentityServer use the following:
new Client
{
ClientId = "...",
ClientName = "...",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "https://local:44352/signin-oidc" },
PostLogoutRedirectUris = { "https://local:44352/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
},
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
}
Now, that this configuration is done, we have to add the IdentityServer to the API.
Configure the Security of the APIs
So, I assume that you have already created the APIs in the API Management Service. Now, click on Settings of the API and scroll down in the section.

Here, select OpenID connect and select from the dropdown list your Identity Server. Then, press the Save button.
Configure the Design of the APIs
Next step is to validate the request. For that, we want to be sure that the request contains a valid JWT token. The token is generate from the Identity Server for each session and user when the user logs in the application. When the application calls the API, the HttpClient
has to include in the header this user token.
So, I have to configure the Inbound processing to validate the JWT token and only if it is valid, the API Manager proceeds with the request. In the other case, the API Manager returns an HTTP 401
. Now, click on All operations and then open the API design.

Now, Add policy and I have the following screen.

Select Validate JWT. Now, the configuration is:
- Validate by: Header
- Header name: Authorization
- Failed validation HTTP code: 401 – Unauthorized
- Open ID Urls: https://youridsrv/.well-known/openid-configuration

Also, as a reminder, you have to configure the CORS. To add a bit more security to the output of your API, I like to set some parameters (see my other post about it). Then, the Policies of the API looks like the following configuration:
<policies>
<inbound>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401"
require-scheme="Bearer" output-token-variable-name="jwt">
<openid-config url="https://yourids/.well-known/openid-configuration" />
</validate-jwt>
<cors>
<allowed-origins>
<origin>*</origin>
</allowed-origins>
<allowed-methods preflight-result-max-age="300">
<method>GET</method>
<method>POST</method>
</allowed-methods>
<allowed-headers>
<header>*</header>
</allowed-headers>
<expose-headers>
<header>*</header>
</expose-headers>
</cors>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<set-header name="Strict-Transport-Security" exists-action="override">
<value>max-age=31536000</value>
</set-header>
<set-header name="X-XSS-Protection" exists-action="override">
<value>1; mode=block</value>
</set-header>
<set-header name="Content-Security-Policy" exists-action="override">
<value>script-src 'self'</value>
</set-header>
<set-header name="X-Frame-Options" exists-action="override">
<value>deny</value>
</set-header>
<set-header name="X-Content-Type-Options" exists-action="override">
<value>nosniff</value>
</set-header>
<set-header name="Expect-Ct" exists-action="override">
<value>max-age=604800,enforce</value>
</set-header>
<set-header name="Cache-Control" exists-action="override">
<value>none</value>
</set-header>
<set-header name="X-Powered-By" exists-action="delete" />
<set-header name="X-AspNet-Version" exists-action="delete" />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Redirect all HTTP request to HTTPS
So, Microsoft says that currently API Management Service doesn’t support HSTS header.
You can configure each API to listen on HTTP
, HTTPS
or both but this does not support redirection, if you configure an API to only listen on https and sends http request you will get 404 from API Management.
However, you can add an input policy which can redirect all HTTP
calls to HTTPS
. This is the most recommended approach.
<inbound>
<choose>
<when condition="@(context.Request.OriginalUrl.Scheme.Equals("http"))">
<return-response>
<set-status code="302" reason="Requires SSL" />
<set-header exists-action="override" name="Location">
<value>
@("https://" + context.Request.OriginalUrl.Host + context.Request.OriginalUrl.Path)
</value>
</set-header>
</return-response>
</when>
</choose>
</inbound>
Configure Blazor
Now, it is time to add some configuration to the Blazor project. The main problem in Blazor is how to have access to the user token. If you google, you find a lot of solutions and most of them are quite complicated. Fortunately, Microsoft gives us, recently, a quite simple way. In the API Service, I have to inject IAccessTokenProvider
that allows me to read the token.
First, the namespaces we have to use. Thanks to NET6, I can create a GlobalUsing.cs
to add all the packages I want to use across the project.
global using Microsoft.AspNetCore.Authorization;
global using Microsoft.AspNetCore.Components.Web;
global using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
global using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
global using Microsoft.Extensions.Configuration;
Appsettings.json
Now, I want to save the settings for the API URL and the HttpClient settings in the appsettings.json
. So, my settings is like that
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"oidc": {
"Authority": "https://your-identity-server-url/",
"ClientId": "220005UI",
"ResponseType": "code",
"DefaultScopes": [
"openid",
"profile",
"roles",
"email",
"offline_access",
"220005_api"
],
"PostLogoutRedirectUri": "authentication/logout-callback",
"RedirectUri": "authentication/login-callback"
},
"Api": {
"EndpointsUrl": "https://api.psc.com/5/v1/",
"Scope": "my_api"
},
"ApplicationSettings": {
"AuthorizedUrls": [
"https://localhost:7241"
],
"Scopes": [
"my_api"
],
"SubscriptionKey": "3251"
}
}
The oidc
configuration is for the connection with Identity Server as required for the AddOidcAuthentication
. To recognise the user’s roles, I’m using my package PSC.Blazor.AuthExtensions.
To read the configuration for the application, this is model called ApplicationSettingsModel
public class ApplicationSettingsModel
{
public Applicationsettings ApplicationSettings { get; set; }
public string SubscriptionKey { get; set; }
}
public class Applicationsettings
{
public string[] AuthorizedUrls { get; set; }
public string[] Scopes { get; set; }
}
Program.cs
Now, I can change the Program.cs to read the configuration and set up the HttpClient
correctly.
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
#region Read configuration
string apiEndpoint = builder.Configuration["Api:EndpointsUrl"];
string apiScope = builder.Configuration["Api:Scope"];
ApplicationSettingsModel settings = new ApplicationSettingsModel();
builder.Configuration.Bind(settings);
#endregion
#region Dependecy injection
builder.Services.AddTransient(_ =>
{
return builder.Configuration.GetSection("ApplicationSettings").Get<ApplicationSettingsModel>();
});
builder.Services.AddScoped<APIService>();
#endregion
#region Configure HTTP Client
builder.Services.AddHttpClient("myAPI", cl =>
{
cl.BaseAddress = new Uri(apiEndpoint);
})
.AddHttpMessageHandler(sp =>
{
var handler = sp.GetService<AuthorizationMessageHandler>()
.ConfigureHandler(
authorizedUrls: settings.ApplicationSettings.AuthorizedUrls,
scopes: settings.ApplicationSettings.Scopes
);
return handler;
});
builder.Services.AddScoped(sp => sp.GetService<IHttpClientFactory>().CreateClient("myAPI"));
#endregion
#region Configure Authentication and Authorization
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("oidc", options.ProviderOptions);
options.UserOptions.RoleClaim = "role";
})
.AddAccountClaimsPrincipalFactory<MultipleRoleClaimsPrincipalFactory<RemoteUserAccount>>();
builder.Services.AddAuthorizationCore();
#endregion
await builder.Build().RunAsync();
The configuration of the HttpClient
uses AddHttpMessageHandler
that coming from the Microsoft Authorization
namespace.
Create the API service with authentication
Finally, the API Service implementation. Here, in the constructor we need the IAccessTokenProvider
to obtain the user’s token. Also, I inject the ApplicationSettingsModel
to have the configuration I need.
public class APIService
{
private readonly HttpClient _httpClient;
private readonly IAccessTokenProvider _accessToken;
private readonly JsonSerializerOptions _options;
public APIService(HttpClient httpClient, IAccessTokenProvider accessToken,
ApplicationSettingsModel settings)
{
_httpClient = httpClient;
_httpClient.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
_httpClient.DefaultRequestHeaders.Add("Apim-Subscription-Key", settings.SubscriptionKey);
_options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
_accessToken = accessToken;
}
public async Task<APIResponse> GetAttributeAsync(APIRequest apirequest)
{
try
{
var tokenResult = await _accessToken.RequestAccessToken();
tokenResult.TryGetToken(out var token);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"yourAPI");
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token.Value);
var content =
new StringContent(JsonSerializer.Serialize(apirequest), Encoding.UTF8, "application/json");
request.Content = content;
HttpResponseMessage responseMessage;
responseMessage = await _httpClient.SendAsync(request);
responseMessage.EnsureSuccessStatusCode();
if (responseMessage.IsSuccessStatusCode)
{
var responseContent = await responseMessage.Content.ReadAsStringAsync();
#if DEBUG
Console.WriteLine("[GetAttributeAsync] API Response: " + responseContent);
#endif
return JsonSerializer.Deserialize<APIResponse>(responseContent, _options);
}
else
return new APIResponse() { Success = false };
}
catch (Exception ex)
{
return new APIResponse() { Success = false };
}
}
}
The important part is how I read the token. The code is here
var tokenResult = await _accessToken.RequestAccessToken();
tokenResult.TryGetToken(out var token);
Using the instance of IAccessTokenProvider
, I can read the user’s token and pass it in the header of the HttpClient
.
So, now we can test the solution. It works in my end! 😁
Wrap up
In conclusion, in this post I showed how to call an Azure API Management protected with Identity Server from Blazor. I sent a lot of time to sort out how to do it. I hope this code it is useful to someone else!
Happy coding!