Google login on .NET backend with React application in frontend - reactjs

EDIT (Thank #possum)
The code under work properly.
TIMEWASTER ALERT
In the option of .AddGoogle(), you can specify a custom callback. This is the callback used in the authentication mechanism between .AddGoogle and the frontend.
In other words, this callback is "internal" and not make by us.
The callback enpoint must are specify in ConfigureExternalAuthenticationProperties("Google", callback url, endpoint after user signin)
I have some issue to make functional a very simple function! Signin on my backend with google (and some other provider).
Before start, you have all of my apologize, the solution to my issue is probably evident for anyone with a good experience on react. It's my first project with react.
So I've read some articles on how integrate Google signin/signup on a backend with NET core and React in frontend. And easy I've read issues found here ...
The code in controller :
[HttpPost("google-login")]
[AllowAnonymous]
[OpenApiOperation("Login / Signup by an external provider.", "Connection with Google, Microsoft, etc")]
public IActionResult GoogleLogin()
{
var redirectUrl = $"https://localhost:5001/api/authentication/google-login-callback";
var properties = _authenticationService.ConstructHandleForGoogleLogin("Google", redirectUrl);
return Challenge(
properties,
"Google");
}
[HttpGet("google-login-callback")]
[AllowAnonymous]
[OpenApiOperation("Callback of agent of login provider.", "Connection with Google, Microsoft, etc")]
public async Task<ActionResult> GoogleLoginCallback()
{
var authenticateResult = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
throw new Exception();
}
The code in authentication service :
public AuthenticationProperties ConstructHandleForGoogleLogin(string provider, string redirectUrl)
{
if (provider.ToLowerInvariant() != "google")
throw new InvalidOperationException("Bad provider requested, this is only for Google");
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
properties.AllowRefresh = true;
return properties;
}
The code for the configuration :
internal static class Startup
{
private static readonly ILogger _logger = Log.ForContext(typeof(Startup));
internal static IServiceCollection AddGoogleAuthentication(this IServiceCollection services, IConfiguration config)
{
_logger.Information("Third partie authentication enabled : {0}", "Google");
services.AddOptions<AuthenticationGoogleSettings>()
.BindConfiguration($"SecuritySettings:{nameof(AuthenticationGoogleSettings)}")
.ValidateDataAnnotations()
.ValidateOnStart();
services
.AddAuthorization()
.AddAuthentication(authentication =>
{
authentication.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
authentication.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogle(googleOptions =>
{
googleOptions.ClientId = config["SecuritySettings:AuthenticationGoogleSettings:ClientId"];
googleOptions.ClientSecret = config["SecuritySettings:AuthenticationGoogleSettings:ClientSecret"];
//googleOptions.CallbackPath = "/api/authentication/google-login-callback"; <- TIMEWAST ALERT
googleOptions.AuthorizationEndpoint += "?prompt=consent";
googleOptions.AccessType = "offline";
googleOptions.Scope.Add("profile");
googleOptions.SignInScheme = IdentityConstants.ExternalScheme;
});
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Strict;
});
return services;
}
}
The code in Frontend :
<form method='POST' action={`https://localhost:5001/api/authentication/google-login`} >
<IconButton
type='submit'
name='provider'
value='Google'>
<Iconify icon="eva:google-fill" color="#DF3E30" />
</IconButton>
</form>

Related

Using a blazor server with signalR as a relay server

The goal is to use a Blazor server as a relay server using signalR.
I have little to no experience with blazor servers before this.
The Idea would be to connect a Winform/Xamarin client to this server, target the recipient using a name/id from an existing database, and relay the necessary info.
Hub:
[Authorize]
public class ChatHub : Hub
{
public Task SendMessageAsync(string user, string message)
{
//Context.UserIdentifier
Debug.WriteLine(Context.UserIdentifier);
Debug.WriteLine(Context?.User?.Claims.FirstOrDefault());
return Clients.All.SendAsync("ReceiveMessage", user, message); ;
}
public Task DirectMessage(string user, string message)
{
return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}
}
As per documentation I'm trying to set the Context.UserIdentifier, I do however struggle with the authentication part. My program.cs looks like this:
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
services.AddTransient<IUserIdProvider, MyUserIdProvider>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
//var accessToken = context.Request.Query["access_token"];
var accessToken = context.Request.Headers["Authorization"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/chathub"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSignalR();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.MapBlazorHub();
app.MapHub<ChatHub>("/chathub");
app.MapFallbackToPage("/_Host");
app.Run();
As for my Client (a winform test client) I tried something like this:
HubConnection chatHubConnection;
chatHubConnection = new HubConnectionBuilder()
.WithUrl("https://localhost:7109/chathub", options =>
{
options.AccessTokenProvider = () => Task.FromResult(token);
})
.WithAutomaticReconnect()
.Build();
private async void HubConBtn_Click(object sender, EventArgs e)
{
chatHubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
this.Invoke(() =>
{
var newMessage = $"{user}: {message}";
MessagesLB.Items.Add(newMessage);
});
});
try
{
await chatHubConnection.StartAsync();
MessagesLB.Items.Add("Connected!");
HubConBtn.Enabled = false;
SendMessageBtn.Enabled = true;
}
catch (Exception ex)
{
MessagesLB.Items.Add(ex.Message);
}
}
As a first step I'm just trying to authenticate a user/check that it's in the live database, if so connect and fill out: Context.UserIdentifier so I can use this within the Hub. I understand that I probably need a middleware however I don't really know exactly how to test a connectionId/Jwt token or similar to get the user/connection.
Any nudge in the right direction would be appreciated.
If I understand your question you don't know where and how to generate a JWT token.
For me the JWT token should be generated from the server, your hub.
POST api/auth and in the playload you give login + SHA256 password and returns JWT token.
Once you checked the user auth is correct in you DB you can issue the token.
To generate a JWT token I use this piece of code.
public string GenerateToken(IConfiguration Config, DateTime? expire)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userName),
new Claim(JwtRegisteredClaimNames.Jti, _id),
new Claim(ClaimsIdentity.DefaultRoleClaimType, role)
};
// ClaimsIdentity.DefaultRoleClaimType
var bytes = Encoding.UTF8.GetBytes(Config["jwt:Secret"]);
var key = new SymmetricSecurityKey(bytes);
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
var token = new JwtSecurityToken(
//Config.GetValue<string>("jwt:Issuer"),
//Config.GetValue<string>("jwt:Issuer") + "/ressources",
claims: claims,
expires: DateTime.Now.AddMinutes(Config.GetValue<int>("jwt:ExpireMinute")),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
#edit
Look here to allow JWT for SignalR
https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-6.0
I also added this.
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
The easiest solution would be to use something like IdentityServer to handle the authentication. It's a free solution, also .NET based which takes very little configuration effort to offer you simple client credentials authentication and generate the token for you.
I did basically exactly what you're asking here: A WinForms application connecting to my signalR hub application on a remote server, using Bearer token - but I also have OIDC/OAUTH implemented with third party user account login.
IdentityServer offers a great repository of full examples that showing you all the flow - and with just a few lines of code changed, you have a fullblown authentication system, which can be enhanced easily.
With IdentityServer you get everything, even the corresponding extension methods that enable your signalR hub application to create the claims principal (aka user) from the claims included within your token.
Here you'll find all the examples and docs:
https://github.com/IdentityServer/IdentityServer4
If you hit any walls, just reply here and I'll try to help.

Pass JWT token from angular HttpClient to access secure .NET Core Web API

I need to call secure Web API from Angular 9 application by presenting the token. I am using Angular with .NET CORE 3.1 Web API. I have managed to generate Azure B2C token but stuck to call secure web api as I got CORS error.
Angular component calling Web API end-point
testAPI1(){
console.log("calling test API ...");
const myheaders = new HttpHeaders({
'Content-Type': 'application/json; charset=utf-8',
'Authorization': this.authService.accessToken
});
this.http.get('https://localhost:5001/txn/v1/Dashboard/GetMessage', {headers: myheaders})
.subscribe((data)=>{
console.warn(data);
})
}
Auth Service
#Injectable()
export class AuthService implements OnInit{
constructor(
private oauthService: OAuthService,
private router: Router
){// other code}
public get accessToken() {
return this.oauthService.getAccessToken();
}
Web API controller & endpoint
[Authorize]
[Route("txn/v1/[controller]/[action]")]
[EnableCors("CorsPolicy")]
[ApiController]
public class DashboardController : ControllerBase
{
[HttpGet]
public ActionResult<HelloMessage> GetMessage()
{
var result = new HelloMessage()
{
GivenName = "james",
ReturnMessage = "Dashboard# Hello, Welcome to Digital tech"
};
return result;
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//JWT Authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(jwtConfig =>
{
jwtConfig.Audience = Configuration["AzureAdB2C:ResourceId"];
jwtConfig.Authority = $"{Configuration["AzureAdB2C:Instance"]}{Configuration["AzureAdB2C:TanantId"]}";
jwtConfig.RequireHttpsMetadata = false;
jwtConfig.SaveToken = true;
jwtConfig.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer =true,
ValidateAudience = true,
ValidateLifetime = true
};
});
//CORS policy
services.AddCors(options =>
options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin()));
error
Policies for CORS can be a bit finicky. So I would recommend maybe trying for a pretty open CORS policy (Which isn't too dangerous given you are using header authentication and not a cookie).
So your configure services method should look like so :
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
}
And then your Configure method should be something like :
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCors( options => options.WithOrigins("http://example.com").AllowAnyMethod() );
app.UseMvc();
}
Note that the order inside the Configure method is important. The call to CORS must be relatively early on, if not the first middleware in your pipeline.
If that works, then work backwards to slowly add policies and see which one breaks. CORS can be really finicky so it works better to allow everything in a basic example and then add thing slowly in.
More reading here : https://dotnetcoretutorials.com/2017/01/03/enabling-cors-asp-net-core/

.net core 3.1 Google SSO Callback url not hit

I followed a link to achieve google SSO github.com/aspnet/Security/issues/1370. But even after successful login it is taking me to redirect uri mentioned in authentication property. It is not taking to the callback url. Could someone help on this? Our application is a .net core 3.1 with IdentityServer4.
Am expecting signinoauth2 API to be hit after google login, but thats not happening.
I could see a network call from browser with below format and getting correlation error.
https://localhost:44368/signinoauth2?state=&code=&scope=***&prompt=none
Exception: Correlation failed.
Show raw exception details
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler.HandleRequestAsync()
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Soulbook.Api.Startup+<>c+<b__5_1>d.MoveNext() in Startup.cs
await next.Invoke();
Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
PFB my code for reference,
[HttpGet]
[Authorize(AuthenticationSchemes = GoogleDefaults.AuthenticationScheme)]
[Route("/Feed")]
public ActionResult Feed()
{
return Ok();
}
[HttpGet]
[Route("/signin")]
public ActionResult SignIn()
{
var authProperties = new AuthenticationProperties
{
RedirectUri = "/"
};
return new ChallengeResult(GoogleDefaults.AuthenticationScheme, authProperties);
}
[HttpPost]
[Route("/signinoauth2")]
public ActionResult<LoginResponse> signinoauth2Async([FromForm]object data)
{
return Ok();
}
Startup.cs
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie(o => {
o.LoginPath = "/signin";
o.LogoutPath = "/signout";
o.ExpireTimeSpan = TimeSpan.FromDays(7);
})
.AddGoogle(o => {
o.ClientId = "***";
o.ClientSecret = "**";
o.SaveTokens = true;
o.CallbackPath = "/signinoauth2";
});
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes(GoogleDefaults.AuthenticationScheme)
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
}).AddNewtonsoftJson();
EDIT: Having signinoauth2 in any one of the below formats also doesnt help.
[HttpGet]
[Route("/signinoauth2")]
public ActionResult<LoginResponse> signinoauth2Async(string state, string code, string scope, string prompt)
{
return Ok();
}
[HttpPost]
[Route("/signinoauth2")]
public ActionResult<LoginResponse> signinoauth2Async(string state, string code, string scope, string prompt)
{
return Ok();
}
I assume that you want to get Google user information in your enpoint?
Then what you have to do is configure the external authentication properties. And thanks to this you are going to be able to get the user on your redirect endpoint.
[HttpGet("login/google/")]
[AllowAnonymous]
public async Task<IActionResult> LoginGoogle()
{
var properties = _signInManager.ConfigureExternalAuthenticationProperties(GoogleDefaults.AuthenticationScheme, "/api/identity/google-redirect");
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
}
What you configured on startup is a callback route which gets handled by Middleware and never hits the endpoint in your controller. What you want to achive is get user on redirect route like this
[HttpGet("google-redirect")]
[AllowAnonymous]
public async Task<IActionResult> CallbackGoogle()
{
var info = await _signInManager.GetExternalLoginInfoAsync();
return Ok();
}
It sounds like you aren't actually being properly authenticated, if you were the app would redirect to the landing page whose controller I assume has an [Authorize] attribute. Could you have possibly forgotten to add yourself as a user in the db that your identity server is referencing?

How can I authenticate and authorize Angular get or post requests using AspNetCore 2.2?

I have setup an dotnet angular project and then implemented authentication as follows in the StartUp.cs file.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IPasswordHasher<CustomUser>,
PasswordHasherWithOldMembershipSupport<CustomUser>>();
services.AddIdentity<CustomUser, IdentityRole>()
.AddEntityFrameworkStores<AuthenticationContext<CustomUser>>()
.AddDefaultUI()
.AddDefaultTokenProviders();
var connection = configuration.GetConnection("Authentication");
services.AddDbContext<AuthenticationContext<CustomUser>>(options =>
options.UseSqlServer(connection));
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<AuthMessageSender.ISmsSender, AuthMessageSender>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
...
}
IdentityHostingStartUp.cs file which runs upon startup to configure authentication.
public class IdentityHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) =>
{
services.AddAuthentication();
services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.RequireUniqueEmail = false;
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(15);
options.LoginPath = "/Identity/Account/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
});
});
}
}
I have a custom redirect in my angular code to go to the authenticate page if the user is not logged in.
import { Inject, Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class AuthService {
constructor(http: HttpClient, #Inject('BASE_URL') baseUrl: string) {
http.get<Boolean>(baseUrl + "api/Home/Status").subscribe((authenticated) => {
if (!authenticated) {
window.location.href = baseUrl + "/Identity/Account/Login";
}
});
}
And finally, my HomeController code to check the authenticated status of the logged in user.
[HttpGet("[action]"), AllowAnonymous]
public Boolean Status()
{
var user = _accessor.HttpContext.User;
return User.Identity.IsAuthenticated;
}
The Status(or any other called api controller actions) action always has a null user name, user claims, and IsAuthenticated always returns false even after logging in.
This is driving me up the wall. I have read as many post and tried as many options as I could find and nothing seems to work.
At some point, I noticed that the user name was filled as expected. I thought it was solved. However, since then it has stopped working even though I haven't changed anything and I can't solve this issue.
This happens when your Debug settings are set to EnableSSL unchecked. This would also become and issue if you were to deploy without SSL enabled. As soon as I enabled SSL as in the picture below the application identity cookie was included in the ajax requests.

How to force authentication to run so that the principal is available for other ASP.NET Core middleware?

I am trying to add a middleware to implement throttling in my Web API based on client id. This Web API is protected by Identity Server 4 and the JWT authentication handler.
The problem is that Context.User.Claims is always empty when my middleware runs.
I understand that the Jwt handler only gets called when the request hits the Authorize attribute.
Thus, my question is, how can I "force" the Jwt handler to run sooner in the pipeline so that my middleware gets the call after the token is validated and the client_id claim is available in the context principal?
Thanks for any help you can give me.
The code that setups the Web API is as follows:
public void ConfigureServices(IServiceCollection services)
{
// Validation
SmartGuard.NotNull(() => services, services);
// Log
this.Logger.LogTrace("Application services configuration starting.");
// Configuration
services
.AddOptions()
.Configure<ServiceConfiguration>(this.Configuration.GetSection(nameof(ServiceConfiguration)))
.Configure<TelemetryConfiguration>(this.Configuration.GetSection(nameof(TelemetryConfiguration)))
.Configure<TableStorageServiceConfiguration>(this.Configuration.GetSection(nameof(TableStorageServiceConfiguration)))
.UseConfigurationSecrets();
ServiceConfiguration serviceConfiguration = services.ResolveConfiguration<ServiceConfiguration>();
// Telemetry (Application Insights)
services.AddTelemetryForApplicationInsights();
// Memory cache
services.AddDistributedMemoryCache();
// MVC
services.AddMvc();
// Identity
services
.AddAuthorization(
(options) =>
{
options.AddPolicy(
Constants.Policies.Settings,
(policy) =>
{
policy.RequireClaim(Constants.ClaimTypes.Scope, Scopes.Settings);
});
});
// NOTE:
// We are using the JWT Bearer handler here instead of the IdentityServer handler
// because version 2.3.0 does not handle bearer challenges correctly.
// For more info: https://github.com/IdentityServer/IdentityServer4/issues/2047
// This is supposed to be fixed in version 2.4.0.
services
.AddAuthentication(Constants.AuthenticationSchemes.Bearer)
.AddJwtBearer(
(options) =>
{
options.Authority = serviceConfiguration.IdentityServerBaseUri;
options.Audience = Constants.ApiName;
options.RequireHttpsMetadata = false;
options.IncludeErrorDetails = true;
options.RefreshOnIssuerKeyNotFound = true;
options.SaveToken = true;
options.Events = new JwtBearerEvents()
{
OnChallenge = HandleChallenge
};
});
// Web API Versioning
services.AddApiVersioning(
(options) =>
{
options.DefaultApiVersion = new Microsoft.AspNetCore.Mvc.ApiVersion(ApiVersions.DefaultVersion.Major, ApiVersions.DefaultVersion.Minor);
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
});
// Setup Throttling
services
.AddThrottling()
.AddClientRateHandler(this.Configuration.GetSection(nameof(ClientRateThrottlingOptions)));
// Routes analyzer
// Creates the /routes route that lists all the routes configured
services.AddRouteAnalyzerInDevelopment(this.CurrentEnvironment);
// Add the managers
services.AddManagers();
// Background services
services.AddBackgroundService<StorageSetupService>();
// Log
this.Logger.LogTrace("Application services configuration completed.");
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Validation
SmartGuard.NotNull(() => app, app);
SmartGuard.NotNull(() => env, env);
// Log
this.Logger.LogTrace("Application configuration starting.");
// Error handling (Telemetry)
app.UseTelemetryExceptionHandler();
// Authentication
app.UseAuthentication();
// Register the throttling middleware
app.UseThrottling();
// MVC
app.UseMvc(
(routes) =>
{
// Routes analyzer
routes.MapRouteAnalyzerInDevelopment(env);
});
// Log
this.Logger.LogTrace("Application configuration completed.");
}
The relevant middleware code is as follows:
internal class ClientRateMiddleware : IClientRateThrottlingMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
(...)
Claim claim = context.User.FindFirst("client_id");
// Claim is always null here because the Jwt handler has not run
(...)
}
}
OK, so I think I have kind of cracked this one. I think #Hugo Quintela Ribeiro is right about the authorization only occurring when the [Authorize] filter is hit, or when a controller that does not [Allow Anonymous] is hit in the case that authorization is set for the whole app. This of course happens at the controllers, and not in the middleware.
It turns out you can actually force authentication to occur in the middleware. I tried a couple of things like the following with no success.
await context.AuthenticateAsync();
await context.AuthenticateAsync("Custom"); //name of my jwt auth
In the end, I had to inject IAuthorizationPolicyProvider and IPolicyEvaluator to get the default policy and authenticate it.
using cpDataORM;
using cpDataServices.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks;
namespace cpDataASP.Middleware
{
public class LocalizationAndCurrencyMiddleware
{
private readonly RequestDelegate _next;
public LocalizationAndCurrencyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IUserService _userService, ILoginContextAccessor loginContext, IAuthorizationPolicyProvider policyProvider, IPolicyEvaluator policyEvaluator)
{
var policy = await policyProvider.GetDefaultPolicyAsync();
await policyEvaluator.AuthenticateAsync(policy, context);
var localizationResources = await _userService.GetLocalizationResources();
loginContext.Timezone = localizationResources.Timezone;
CultureInfo.CurrentCulture = localizationResources.Culture;
await _next.Invoke(context);
}
}
}

Resources