Reputation: 156
I have built an API that controls some smart home stuff. To prevent the whole internet from doing so, I added authentication using JWT / Bearer. The API contains endpoints for the smart home stuff aswell as some user management: API endpoints for users
The login will return a JWT token if credentials were valid. It is also built using .NET 6:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(x =>
{
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
};
});
Login Controller:
[HttpPost]
public async Task<IActionResult> Login([FromBody] UserLogin login)
{
var user = await _userService.GetUser(login.Username);
if (user is not null && _userService.IsPasswordCorrect(user, login.Password))
{
var tokens = await _userService.GetJwtAndRefreshToken(user);
return Ok(new LoginResponse { JWT = tokens.Jwt, RefreshToken = tokens.Refreshtoken });
}
return Unauthorized("Wrong username or password!");
}
Now I am trying to build a frontend for this app using blazor. When creating the app, i used the option "individual user accounts" for authentication. It is documented here: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/standalone-with-authentication-library?view=aspnetcore-6.0&tabs=visual-studio
This created the following in the blazow WASM app:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddOidcAuthentication(options =>
{
// Configure your authentication provider options here.
// For more information, see https://aka.ms/blazor-standalone-auth
builder.Configuration.Bind("Local", options.ProviderOptions);
});
await builder.Build().RunAsync();
appsettings.json looks like this:
{
"Local": {
"Authority": "https://localhost:7110/login",
"ClientId": "33333333-3333-3333-33333333333333333"
}
}
I changed the Authority to my login api url, but doesn't seem to be enough. Clicking on the login button that was added by default fires this request: Request
Is there a simple way to use the MS Authorization framework with my custom api?
Upvotes: 1
Views: 3129
Reputation: 7891
I spent great amount of time on this. These are my notes from it. Note that I am using IdentityServer. Probably a lot of stuff will be different for you. But it should at least guide you what to check.
It works (for me), but best-practise is not garantee.
My API address is on port 5001
, Client is on port 5101
For Client project
var clientBaseAddress = new Uri(builder.Configuration["apiurl"] ?? throw new ArgumentNullException("apirul is null (reading from config file)"));
builder.Services.AddHttpClient("BlazorApp6.ServerAPI", client =>client.BaseAddress = clientBaseAddress)
.AddHttpMessageHandler(sp =>
{//this is need when api is separated. https://code-maze.com/using-access-token-with-blazor-webassembly-httpclient/
var handler = sp.GetService<AuthorizationMessageHandler>()!
.ConfigureHandler(
authorizedUrls: new[] { builder.Configuration["HttpMessageHandlerAuthorizedUrls"] },
scopes: new[] { "BlazorApp6.ServerAPI" }
);
return handler;
});
builder.Services.AddHttpClient<PublicClient>(client => client.BaseAddress = clientBaseAddress);
Add HttpMessageHandlerAuthorizedUrls
apiurl
to appsettings (example for developement):
"apiurl": "https://localhost:5001",
"HttpMessageHandlerAuthorizedUrls": "https://localhost:5001",
Program.cs AddApiAuthorization is different (set opt.ProviderOptions.ConfigurationEndpoint)
builder.Services.AddApiAuthorization(
//this line is only when address of api consumer is different
opt => opt.ProviderOptions.ConfigurationEndpoint = builder.Configuration["ApiAuthorizationConfigurationEndpoint"]
).AddAccountClaimsPrincipalFactory<CustomUserFactory>();
Add ApiAuthorizationConfigurationEndpoint
to appsettings
"ApiAuthorizationConfigurationEndpoint": "https://localhost:5001/_configuration/BlazorApp6.Client"
Change launchSetting to different port
"applicationUrl": "https://localhost:5101;http://localhost:5100",
For api project
Add cors to client app
string developmentCorsPolicy = "dev_cors";
services.AddCors(opt =>
{
opt.AddPolicy(name: developmentCorsPolicy, builder =>
{
builder.WithOrigins("https://localhost:5101", "https://localhost:5201")
.WithMethods("GET", "POST", "PUT", "DELETE")
.AllowAnyHeader();
});
});
//...
if (app.Environment.IsDevelopment())
app.UseCors(developmentCorsPolicy);
There is probably some need to add cors for identiy server, but it works without it.
in case it is needed:
services.AddSingleton<ICorsPolicyService>((container) =>
{
var logger = container.GetRequiredService<ILogger<DefaultCorsPolicyService>>();
return new DefaultCorsPolicyService(logger)
{
AllowAll = true
};
});
Change appsettings IdentityServer section to have some info about client.
This info is obtained in OidcController with requests starting _configuration:
"IdentityServer": {
"Clients": {
"BlazorApp6.Client": {
"Profile": "SPA",
"LogoutUri": "https://localhost:5101/authentication/logout-callback",
"RedirectUri": "https://localhost:5101/authentication/login-callback"
},
},
"Key": {
"Type": "Development"
} }
Note that Profile has changed to SPA (instead of IdentityServerSPA, which means hosted)
Upvotes: 1