SignalR JWT claims missing in IUserIdProvider. JWT bearer OnMessageReceived not firing - c#

I'm attempting to set up SignalR with a .NET client and server.
I simply need a custom way to provide the ID of the connected client, and it seems the only way to do this is to populate the claims that are part of the HubConnectionContext argument when implementing the IUserIdProvider interface.
On my client I am constructing a JWT that works as intended (verified that it is constructed correctly).
On my server I have followed the exact instructions provided here
However, the OnMessageReceived callback does not fire, and therefore the token is ignored and the subsequently the claims array is empty in the IUserIdProvider.
It is worth noting that the IUserIdProvider still gets called when the client connects.
This is my client-side JWT generation code:
_hubConnection = new HubConnectionBuilder()
.WithUrl($"{_hubUrl}", options => options.AccessTokenProvider = () =>
{
var jwtHandler = new JwtSecurityTokenHandler();
var credentials = new SigningCredentials(_securityKey, SecurityAlgorithms.HmacSha256);
var jwt = new JwtSecurityToken(claims: new[] { new Claim("Id", _id) }, signingCredentials: credentials);
var tokenString = jwtHandler.WriteToken(jwt);
return Task.FromResult(tokenString);
})
.Build();
This is my Startup ConfigureServices function:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IUserIdProvider, MyIdProvider>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
LifetimeValidator = (before, expires, token, param) =>
{
return true;
},
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = SecurityKey
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// breakpoints never hit...
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrWhiteSpace(accessToken) &&
path.StartsWithSegments("myHub"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(Version, new OpenApiInfo
{
Version = Version,
Title = Name
});
});
}
and then finally my IUserIdProvider implementation:
public class MyIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
var id = connection.User.Claims.First(x => x.Type == "Id").Value;
return id;
}
}
Thanks in advance.

There was a couple of problems that I had to solve to get this working.
Firstly the Hub requires the [Authorize] attribute otherwise OnMessageReceived will never be called.
Secondly, I was missing the app.UseAuthentication() that is also required for the pipeline to function correctly.
After making these changes, my claims were coming across correctly.

Related

.NET Core JWTBearerAuth returning "Unauthorized"

I'm trying to add auth to my .NET Core 3.1 application, however each attempt to use the returned JWT results in 401 Unauthorized. I've gone through a few steps to debug:
I've confirmed that the JWT that my authenticate endpoint returns is valid, using this online validator.
I've re-ordered my UseRouting(), UseAuthentication(), UseAuthorization(), and UseEndpoints() to every possible order.
I've disabled both audience and issuer validation entirely for the time being.
Can someone identify where I've gone wrong, or at the very least, provide some method of properly debugging so I can track the issue down? Thanks. Code below.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services
.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
var key = Encoding.UTF8.GetBytes("thisismycustomSecretkeyforauthentication");
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "issuer",
ValidAudience = "audience",
IssuerSigningKey = new SymmetricSecurityKey(key)
};
});
services
.AddControllers();
.AddMvc(options => { options.EnableEndpointRouting = false; });
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app
.UseStaticFiles()
.UseHsts()
.UseHttpsRedirection()
.UseMvc(routes => routes.MapRoute(name: "default", template: "{controller=App}/{action=Index}/{id?}"))
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseEndpoints(endpoints => { endpoints.MapControllers(); });
if (env.IsDevelopment())
app.UseSpa(spa => spa.UseProxyToSpaDevelopmentServer("https://localhost:22010"));
}
AuthController.cs
[ApiController]
[Authorize]
[Route("[controller]")]
public class AuthController : Controller
{
[AllowAnonymous]
[HttpPost("authenticate")]
public async Task<IActionResult> Authenticate(AuthenticationRequest request)
{
if (request.Username != "test" || request.Password != "admin")
return Unauthorized();
var tokenHandler = new JwtSecurityTokenHandler();
var tokenKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("thisismycustomSecretkeyforauthentication"));
var token = new JwtSecurityToken
(
issuer: null,
audience: null,
claims: new Claim[] { new Claim(ClaimTypes.Name, request.Username) },
expires: DateTime.Now.AddDays(30),
signingCredentials: new SigningCredentials(tokenKey, SecurityAlgorithms.HmacSha256)
);
var tokenResponse = tokenHandler.WriteToken(token);
return Ok(tokenResponse);
}
[HttpGet]
public IActionResult Do()
{
return Ok("Done!");
}
}
Authentication setup looks fine, and your middleware order is per Microsoft Documentation.
Based on the code provided you seem to be missing the actual Authorization options. This is similar to the issue solved in this question.

How State management of claims work in Blazor Server?

I have implemented Identity Server for Auth Code Flow.
What is correct way to persist the claims (in OnTicketReceived or OnTicketValidated as shown below), so that in subsequent calls
to Blazor pages, I could receive User aka ClaimPrincipal populated for
my use?
Here is code of middleware of my resource server:
public void ConfigureServices(IServiceCollection services)
{
//....
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
options.SignIn.RequireConfirmedEmail = false;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<SomeContext>()
.AddDefaultTokenProviders();
Middleware integration:
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
options.DefaultSignOutScheme = "oidc";
})
.AddCookie("cookie", options =>
{
options.Cookie.Name = "__Host-bff";
options.Cookie.SameSite = SameSiteMode.Strict;
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc.code";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.ResponseMode = "query";
options.GetClaimsFromUserInfoEndpoint = true;
options.MapInboundClaims = false;
options.SaveTokens = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
//Critical Parts
options.TokenValidationParameters = new()
{
NameClaimType = "name",
RoleClaimType = "role"
};
Authenticated callback from identity server is as following:
options.Events.OnTicketReceived = async n =>
{
var serviceProvider = n.HttpContext.RequestServices;
var accountService = serviceProvider.GetService<IAccountService>() ?? throw new ArgumentNullException("serviceProvider.GetService<IAccountService>()");
I tried using BlazoredSessionStorage etc. but it seems too early to
invoke that. We have to wait until OnPrerender or OnInit
I also tried CustomTokenStore. But how does the claim from cookie
come back to server?
var svc = n.HttpContext.RequestServices.GetRequiredService<IUserAccessTokenStore>();
if (n.Principal != null)
{
var userName = n.Principal.FindFirst(x => x.Type == "name")?.Value;
await accountService.UserCreateAsync(new NewAccount
{
Username = userName,
FirstName = userFirstName,
LastName = userLastName,
//ContactId = 100,
TenantId = 1
});
await (authProvider as SomeAuthenticationStateProvider).LoginAsync(new AuthenticationLogin { Username = userName }, 24 * 60);
}
};
public class CustomTokenStore : IUserAccessTokenStore
{
ConcurrentDictionary<string, UserAccessToken> _tokens = new ConcurrentDictionary<string, UserAccessToken>();
public Task ClearTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
_tokens.TryRemove(sub, out _);
return Task.CompletedTask;
}
public Task<UserAccessToken> GetTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
_tokens.TryGetValue(sub, out var value);
return Task.FromResult(value);
}
public Task StoreTokenAsync(ClaimsPrincipal user, string accessToken, DateTimeOffset expiration, string refreshToken = null, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
var token = new UserAccessToken
{
AccessToken = accessToken,
Expiration = expiration,
RefreshToken = refreshToken
};
_tokens[sub] = token;
return Task.CompletedTask;
}
}
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.ClaimActions.Add(new CustomClaimsFactory(
"userName",
"XXXXX#outlook.com"
));
options.TokenValidationParameters = new TokenValidationParameters
{
// Instead of using the default validation (validating against a single issuer value, as we do in
// line of business apps), we inject our own multitenant validation logic
ValidateIssuer = false,
// If the app is meant to be accessed by entire organizations, add your issuer validation logic here.
//IssuerValidator = (issuer, securityToken, validationParameters) => {
// if (myIssuerValidationLogic(issuer)) return issuer;
//}
};
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = context =>
{
// If your authentication logic is based on users then add your logic here
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/Error");
context.HandleResponse(); // Suppress the exception
return Task.CompletedTask;
},
// If your application needs to do authenticate single users, add your user validation below.
//OnTokenValidated = context =>
//{
// var claims = new List<Claim>
// {
// new Claim(ClaimTypes.Role, "superadmin")
// };
// var appIdentity = new ClaimsIdentity(claims);
// context.Principal.AddIdentity(appIdentity);
// return Task.CompletedTask;
// //return myUserValidationLogic(context.Ticket.Principal);
//}
};
});
While CustomActionFactory class is defined below
public class CustomClaimsFactory : ClaimAction
{
string _ClaimType;
string _ValueType;
public CustomClaimsFactory(string claimType, string valueType) : base(claimType, valueType)
{
_ClaimType = claimType;
_ValueType = valueType;
}
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
{
identity.AddClaim(new Claim(_ClaimType, _ValueType, issuer));
}
}```
OnTokenValidated has a method to add the new identity you create with the claim:
OnTokenValidated = ctx =>{ ctx.Principal.AddIdentity(myNewClaim); }

JWT Authentication .NET Core 2.2 doesn't update HttpContext

I've searched through some similar questions, but none helped me. I have the following situation:
Basic JWT authentication in .NET Core 2.2, Startup.cs has its settings as:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
var signingConfigurations = new SigningConfigurations();
services.AddSingleton(signingConfigurations);
var tokenConfigurations = new TokenConfigurations();
new ConfigureFromConfigurationOptions<TokenConfigurations>(
Configuration.GetSection("TokenConfigurations"))
.Configure(tokenConfigurations);
services.AddSingleton(tokenConfigurations);
services.AddAuthentication(authOptions =>
{
authOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
authOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(bearerOptions =>
{
var paramsValidation = bearerOptions.TokenValidationParameters;
paramsValidation.IssuerSigningKey = signingConfigurations.Key;
paramsValidation.ValidAudience = tokenConfigurations.Audience;
paramsValidation.ValidIssuer = tokenConfigurations.Issuer;
// Validates a received token signature
paramsValidation.ValidateIssuerSigningKey = true;
// Verifies if a received token is still valid
paramsValidation.ValidateLifetime = true;
// Tolerance time for token expiration (used if there are timezone differences)
paramsValidation.ClockSkew = TimeSpan.Zero;
});
// Activates token usage on this project
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
Then, to generate the token:
LoginController.cs
if (validCredentials)
{
ClaimsIdentity declarations = new ClaimsIdentity(
new GenericIdentity(userInDB.UserName, "Login"),
new[] {
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N")),
new Claim(JwtRegisteredClaimNames.UniqueName, userInDB.UserName),
new Claim("userid", userInDB.Id, ClaimValueTypes.String),
new Claim("username", userInDB.UserName, ClaimValueTypes.String),
}
);
DateTime creationDate = DateTime.Now;
DateTime expirationDate = creationDate +
TimeSpan.FromSeconds(tokenConfigurations.Seconds);
var handler = new JwtSecurityTokenHandler();
var securityToken = handler.CreateToken(new SecurityTokenDescriptor
{
Issuer = tokenConfigurations.Issuer,
Audience = tokenConfigurations.Audience,
SigningCredentials = signingConfigurations.SigningCredentials,
Subject = declarations,
NotBefore = creationDate,
Expires = expirationDate
});
var token = handler.WriteToken(securityToken);
return new
{
authenticated = true,
created = creationDate.ToString("yyyy-MM-dd HH:mm:ss"),
expiration = expirationDate.ToString("yyyy-MM-dd HH:mm:ss"),
accessToken = token,
message = "OK"
};
}
All I did so far is based on this walkthrough
So far, so good. If I add [Authorize("Bearer")] on a controller, it can be accessed only if I have the bearer on the header of the request.
But as I was implementing an UserSession structure, to intercept any request, go through the HttpContext and fill the UserSession object, it just doesn't work. The responsible method is this:
ApplicationBuilderExtensions.cs
public static IApplicationBuilder UseSessionConfiguration(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<SessionConfigurationMiddleware>();
}
public class SessionConfigurationMiddleware
{
private readonly RequestDelegate _next;
public SessionConfigurationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, IUserSession sessao)
{
if (context.User.Identities.Any(id => id.IsAuthenticated))
{
sessao.UserId = context.User.Claims.FirstOrDefault(x => x.Type == "userid").Value;
sessao.Roles = context.User.Claims.Where(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role").Select(x => x.Value).ToList();
sessao.UserName = context.User.Claims.FirstOrDefault(x => x.Type == "username").Value;
}
// Call the next delegate/middleware in the pipeline
await _next.Invoke(context);
}
}
But, as I debbug this portion of the code (which is correctly being called after any request) the IsAuthenticated property on the HttpContext is always false, in fact, everything is null, or empty on the context.User and I have no idea why.
Am I configuring the jwt wrong?
Is the middleware not correct?
Am I filling the Claims on the jwt correctly?
Why is [Authorize("Bearer")] working correctly if HttpContext is not properly filled?
Are we alone in the universe?
Thanks in advance.

Check IP with JWT Authorization in ASP.NET Core Web Api

Is it possible to check the IP adress when using System.IdentityModel.Tokens.Jwt in an ASP.NET core Web Api application?
I thought about adding a Claim containing the IP of the user that requested it and check it somehow for each request. Normally I would use OnActionExecuting in ASP.NET MVC.
Is there a Middleware/Authorization based solution?
I Create my Jwt Token Claims like this:
private IEnumerable<Claim> getStandardClaims(IdentityUser user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("ipaddress", HttpContext.Connection.RemoteIpAddress.ToString())
};
return claims;
}
this is what the JWT Data look like:
{
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "username",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "5a6b3eb8-ed7f-48c6-b10c-a279ffd4f7c8",
"sub": "username",
"jti": "44c95b53-bfba-4f33-b4c3-834127605432",
"ipaddress": "::1",
"exp": 1542707081,
"iss": "https://localhost:5001/",
"aud": "https://localhost:5001/"
}
Edit: Possible Solution for JWT Claims?
Maybe I have to read the Claims like this (Test code, no null checks ect..):
var auth = HttpContext.Request.Headers.FirstOrDefault(x => x.Key == "Authorization");
string token = auth.Value[0].Split(' ')[1];
JwtTokenService<RefreshToken, string> jwtService = new JwtTokenService<RefreshToken, string>(null);
var principal = jwtService.GetPrincipalFromExpiredToken(token, _config["Jwt:Key"]);
Claim ipClaim = principal.FindFirst(claim => claim.Type == "ipaddress");
This is the GetPrincipalFromExpiredToken Method:
public ClaimsPrincipal GetPrincipalFromExpiredToken(string token, string securityKey)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)),
ValidateLifetime = false
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
var jwtSecurityToken = securityToken as JwtSecurityToken;
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
You can do that (and all other authorization stuff) via Policy-based authorization.
public class IpCheckRequirement : IAuthorizationRequirement
{
public bool IpClaimRequired { get; set; } = true;
}
public class IpCheckHandler : AuthorizationHandler<IpCheckRequirement>
{
public IpCheckHandler(IHttpContextAccessor httpContextAccessor)
{
HttpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
private IHttpContextAccessor HttpContextAccessor { get; }
private HttpContext HttpContext => HttpContextAccessor.HttpContext;
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IpCheckRequirement requirement)
{
Claim ipClaim = context.User.FindFirst(claim => claim.Type == "ipaddress");
// No claim existing set and and its configured as optional so skip the check
if(ipClaim == null && !requirement.IpClaimRequired)
{
// Optional claims (IsClaimRequired=false and no "ipaddress" in the claims principal) won't call context.Fail()
// This allows next Handle to succeed. If we call Fail() the access will be denied, even if handlers
// evaluated after this one do succeed
return Task.CompletedTask;
}
if (ipClaim.Value = HttpContext.Connection.RemoteIpAddress?.ToString())
{
context.Succeed(requirement);
}
else
{
// Only call fail, to guarantee a failure, even if further handlers may succeed
context.Fail();
}
return Task.CompletedTask;
}
}
then add
services.AddSingleton<IAuthorizationHandler, IpCheckHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("SameIpPolicy",
policy => policy.Requirements.Add(new IpCheckRequirement { IpClaimRequired = true }));
});
to your ConfigureServices method.
Now you can annote the controllers on which you want to apply it with [Authroize(Policy = "SameIpPolicy")] or add a global policy:
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizeFilter("SameIpPolicy"))
})
For #Tseng solution,
Set Default Policy
services.AddAuthorization(options =>
{
options.AddPolicy("SAME_IP_POLICY", i => i.Requirements.Add(new IpCheckRequirement { IpClaimRequired = true }));
options.DefaultPolicy = options.GetPolicy("SAME_IP_POLICY") ?? options.DefaultPolicy;
});
I made it work with #Tseng solution by applying [Authorize(Policy = "SameIpPolicy")] on each controller, thanks a lot man !
Just to correct one typo mistake :
Missing semi colon :
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizeFilter("SameIpPolicy"));
})
ClaimsPrincipal.Current.FindFirst(claim => claim.Type == "ipaddr")

How to authorize in ASP.NET Core 2 using jwt

I'm using the [Authorize] attribute for authentification in my controller, but when I get a request to TestMethod I get an error: "500 Internal..".
What am I doing wrong??
That my code from StartUp.cs
services.AddAuthorization(options =>
{
options.DefaultPolicy =
new AuthorizationPolicyBuilder("Identity.Application")
.RequireAuthenticatedUser()
.Build();
});
services
.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters =
new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
SaveSigninToken = true,
ValidateIssuer = true,
ValidIssuer = "http://blabla/",
ValidateAudience = true,
ValidAudience = "http://blabla/",
ValidateLifetime = true,
IssuerSigningKey = blabla.bla(),
ValidateIssuerSigningKey = true,
};
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
services.AddMvc();
And also code from Controller
[Route("test"), HttpPost]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public void Test() { }
Do you have ideas?
I'm using these libraries to generate the token:
System.IdentityModel.Tokens.Jwt;
Microsoft.IdentityModel.Tokens;
If you want to use the [Authorize] attribute you need to make a policy:
//new policy makes [Authorize] availible by claims
services.AddAuthorization((options) => {
options.AddPolicy("MyNewPolicy", policybuilder =>
{
policybuilder.RequireAuthenticatedUser();
policybuilder.RequireClaim("role", "someClaim");
});
});
//usage
[Authorize(Roles = "someClaim")]
public async Task<IActionResult> About(){
}
//to awnsr your comment add a list of claims to your user class ex:
new TestUser
{
SubjectId="1001",
Username="Frank",
Password="password",
Claims= new List<Claim>
{
new Claim("given_name","Frank"),
new Claim("family_name","Underwood"),
new Claim("address","1 addy rd unit 233"),
new Claim("role", "someClaim")
}
}
I ran into a lot of issues when tryout out AddJwtBearer. Finally I found out that making a manual login wasn't that much harder, worked easily and is also easier to debug.
Basically, first I created a helper class for creating and validating tokens. Here is the source code for the class: https://github.com/neville-nazerane/netcore-jwt-sample/blob/master/website/TokenGenerator.cs. Everything you had added in your TokenValidationParameters can go inside this class.
Once you have that, here is a Boiler plate authentication scheme:
public class TokenAuthenticationOptions : AuthenticationSchemeOptions
{
}
public class TokenAuthentication : AuthenticationHandler<TokenAuthenticationOptions>
{
public const string SchemeName = "TokenAuth";
public TokenAuthentication(IOptionsMonitor<TokenAuthenticationOptions> options, ILoggerFactory logger,
UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
return Task.Run(() => Authenticate());
}
private AuthenticateResult Authenticate()
{
string auth, token;
auth = Context.Request.Headers["Authorization"];
if (auth == null) return AuthenticateResult.Fail("No JWT token provided");
var auths = auth.Split(" ");
if (auths[0].ToLower() != "bearer") return AuthenticateResult.Fail("Invalid authentication");
token = auths[1];
try
{
var generator = new TokenGenerator();
var principal = generator.Validate(token);
return AuthenticateResult.Success(new AuthenticationTicket(principal, SchemeName));
}
catch
{
return AuthenticateResult.Fail("Failed to validate token");
}
}
}
Finally, in your start up you can use this scheme this way:
services.AddAuthentication(TokenAuthentication.SchemeName)
.AddScheme<TokenAuthenticationOptions, TokenAuthentication>
(TokenAuthentication.SchemeName, o => { });

Categories