JWT Authentication in a distributed scenario with .NET 5 - .net-core

I'm trying to implement JWT authentication in a .NET 5 WebAPI. Everything works fine until I add more web servers to the application.
How could I store JWT, for example, in a SQL Server database instead of having them in memory on the single server?
Here's the implementation working on a single server:
Startup
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Context.JwtIssuer,
ValidAudience = Context.JwtIssuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Context.JwtSecretKey))
};
}
Controller
[HttpGet("Login")]
public IActionResult Login(string uid, string password)
{
var hash = _userManager.GetPasswordHash(uid);
if (string.IsNullOrEmpty(hash))
{
return NotFound("Account not found");
}
if (!_passwordHasher.Check(hash, password))
{
return Unauthorized("Uid/password not correct");
}
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Context.JwtSecretKey));
var token = new JwtSecurityToken(
issuer: Context.JwtIssuer,
audience: Context.JwtIssuer,
expires: DateTime.Now.AddHours(3),
claims: new List<Claim>() { new Claim(ClaimTypes.Name, uid)},
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}

Related

Blazor Wasm HttpClient Authorisation header not providing claims

I've upgraded a Blazor Wasm app to .Net 6, by creating new projects and then added MS Identity (not Identity Server) from Chris Sainty's blog. Authorisation looks ok & the user can logon. However the claims are not available to the api controller. This is because it looks like the Authorisation header is not being set for the http client call. If I explicitly set the DefaultRequestHeaders.Authorization to the jwt token then I get the claims in the controller ok. I therefore assume the issue is in how I setup the http client (I thought ApiAuthenticationStateProvider would have done this). Any help would be greatly appreciated.
The relevant parts are:
Client: AuthService:
public async Task<LoginResponse> Login(LoginModel loginModel)
{
loginModel.BaskeyKey = await _localStorageService.GetItemAsync<string>(LocalStorageConstants.BasketKey);
var loginAsJson = JsonSerializer.Serialize(loginModel);
var response = await _httpClient.PostAsync("api/Login", new StringContent(loginAsJson,
Encoding.UTF8, "application/json"));
var loginResult = JsonSerializer.Deserialize<LoginResponse>(
await response.Content.ReadAsStringAsync(),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (!response.IsSuccessStatusCode)
{
return loginResult;
}
await _localStorageService.SetItemAsync("authToken", loginResult.Token);
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(loginModel.Email);
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("bearer", loginResult.Token);
_basketStateService.SetBasket(loginResult.LoginResponseDto.BasketDto);
return loginResult;
}
Server Startup:
builder.Services.AddDefaultIdentity<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<LTDbContext>();
builder.Services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier;
});
var jwtSettings = builder.Configuration.GetSection("Jwt");
builder.Services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings["JwtIssuer"],
ValidAudience = jwtSettings["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(jwtSettings["JwtSecurityKey"]))
};
options.MapInboundClaims = false;
});
and client setup for httpClient (I need an authorised & unauthorised client - I think)
builder.Services.AddHttpClient(AuthConstants.Auth, client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
});
builder.Services.AddHttpClient(AuthConstants.NoAuth,
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
builder.Services.AddScoped(sp =>
sp.GetRequiredService<IHttpClientFactory>().CreateClient(AuthConstants.Auth));
and Client - ApiAuthenticationStateProvider
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var savedToken = await _localStorageService.GetItemAsync<string>("authToken");
if (string.IsNullOrWhiteSpace(savedToken))
{
return new AuthenticationState(new ClaimsPrincipal(
new ClaimsIdentity()));
}
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken);
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
}
public void MarkUserAsAuthenticated(string email)
{
var authenticatedUser = new ClaimsPrincipal(
new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, email),
new Claim(ClaimTypes.NameIdentifier, email),
new Claim(ClaimTypes.Email, email)
},
"apiauth"
));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}

SignalR core JWT authentication not working

I'm trying to authenticate with JWT in .NET Core 2.2 with SignalR. I don't get an error message that it doesn't work on either the server or the client-side. I have set breakpoints on the server-side where the authentication is supposed to happen. Does anyone have any idea why it's not working? The JWT is built with an asymmetric signature algorithm.
This is how I generate the JWT:
var utcNow = DateTime.UtcNow;
using (RSA privateRsa = RSA.Create())
{
privateRsa.FromXmlFile(Path.Combine(HttpContext.Current.Server.MapPath("~"),
"Keys",
ConfigurationManager.AppSettings["PrivateKey"]
));
var privateKey = new RsaSecurityKey(privateRsa);
SigningCredentials signingCredentials = new SigningCredentials(privateKey, SecurityAlgorithms.RsaSha256);
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.ID.ToString()),
//new Claim(JwtRegisteredClaimNames.NameId, user.FullName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, utcNow.ToString())
};
var jwt = new JwtSecurityToken(
signingCredentials: signingCredentials,
claims: claims,
notBefore: utcNow,
expires: utcNow.AddMonths(12),
audience: "https://pacsonweb.com",
issuer: "PACSonWEB3 App"
);
return new JwtSecurityTokenHandler().WriteToken(jwt);
This is how i'm authenticating on the server side :
RsaSecurityKey signingKey = new RsaSecurityKey(publicRsa);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(config =>
{
config.RequireHttpsMetadata = true;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = signingKey,
ValidateAudience = true,
ValidAudience = this.Configuration["Tokens:Audience"],
ValidateIssuer = true,
ValidIssuer = this.Configuration["Tokens:Issuer"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true
};
});
This is how I send the JWT with SignalR on the client side:
hubConnection = new HubConnectionBuilder().WithUrl(hubUrl, (opts) =>
{
opts.AccessTokenProvider = () => Globals.GetJWTToken();
opts.HttpMessageHandlerFactory = (message) =>
{
if (message is HttpClientHandler clientHandler)
{
// bypass SSL certificate
clientHandler.ServerCertificateCustomValidationCallback += CheckCertificate;
clientHandler.CheckCertificateRevocationList = false;
}
return message;
};
}).Build();

Adding authentication in api endpoint - ASP.Net Core

I started code own site with backend ASP.NET Core and React. I get a basic code to create an ASP backend with authentication from this
https://jasonwatmore.com/post/2019/10/14/aspnet-core-3-simple-api-for-authentication-registration-and-user-management#running-react
I created an endpoint to my API which allows adding a photo to my server and It's working
[HttpPost("{username}/photos/")]
public IActionResult AddPhoto(String username, [FromForm]IFormFile photo)
{
//add img to static folder
var wwww_root = _webHostEnvironment.WebRootPath;
var path = #"\profiles\" + username + #"\profile_photos\";
var upload_path = wwww_root + path;
var pathForFile = upload_path + photo.FileName;
try
{
using (FileStream fileStream = System.IO.File.Create(pathForFile))
{
photo.CopyTo(fileStream);
fileStream.Flush();
}
}
catch(DirectoryNotFoundException e)
{
return NotFound("This username does not exists");
}
var pathOnServer = "http://" + Request.Host.Value + path + photo.FileName;
var photoImage = _profileService.AddPhoto(username, pathOnServer);
return Ok();
}
but I want to also add authentication to use this endpoint based on the given "username"
i.e. this endpoint returns OK when the Bearer Token is the same as that assigned to the username.
In the given link I found a method configuring the authentication:
public void ConfigureServices(IServiceCollection services)
{
// use sql server db in production and sqlite db in development
if (_env.IsProduction())
services.AddDbContext<DataContext>();
else
services.AddDbContext<DataContext, SqliteDataContext>();
services.AddCors();
services.AddControllers();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
// configure strongly typed settings objects
var appSettingsSection = _configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
// configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
var userId = int.Parse(context.Principal.Identity.Name);
var user = userService.GetById(userId);
if (user == null)
{
// return unauthorized if user no longer exists
context.Fail("Unauthorized");
}
return Task.CompletedTask;
}
};
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
And api endpoint to Authenticate:
[AllowAnonymous]
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]AuthenticateModel model)
{
var user = _userService.Authenticate(model.Username, model.Password);
if (user == null)
return BadRequest(new { message = "Username or password is incorrect" });
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
// return basic user info and authentication token
return Ok(new
{
Id = user.Id,
Username = user.Username,
FirstName = user.FirstName,
LastName = user.LastName,
Token = tokenString
});
}
but I can't translate this into a solution to my problem.
I am asking for tips on how I can do it
EDIT 1
You can check the claims and token in your upload method
In ConfigureService add this
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
In the upload method use this:
// works if the token is in the header
var tokenFromHeader = HttpContext.Request.Headers["Authorization"];
// or try this if .Net Core is < 3.0
var tokenFromAuth = await HttpContext.Authentication.GetTokenAsync("access_token");
// Get Claims
var identity = HttpContext.User.Identity as ClaimsIdentity;
// Get user id from claims.
var userIdClaim = identity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name);
// Use the value.
if (userIdClaim != null && userIdClaim.value != null) {
// your logic here
}
To check the authorization on api call set the attribute [Authorize] on your controller or on your method.
[Authorize]
[HttpPost("{username}/photos/")]
public IActionResult AddPhoto(String username, [FromForm]IFormFile photo)
More info here:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-3.1
The basic code:
Startup.cs -> ConfigureServices
// configure jwt authentication
var jwtSettings = jwtSettingsSection.Get<JwtSettings>();
var key = Encoding.ASCII.GetBytes(jwtSettings.JwtKey);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = jwtSettings.JwtIssuer,
ValidateAudience = true,
ValidAudience = jwtSettings.JwtAudience,
//RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
Startup.cs -> Configure
app.UseAuthentication();
app.UseAuthorization();
Authentication Controller
[AllowAnonymous]
[HttpPost()]
public async Task<IActionResult> AuthenticateUser([FromBody] UserAuthentication userResource)
{
//Your logic of authorization here
//if not authorized
//return Unauthorized();
var tokenString = CreateJwtToken(user);
// return basic user info (without password) and token to store client side
return Ok(new
{
Token = tokenString
});
}
Create JWT Token
public string CreateJwtToken(McUsers user)
{
var key = Encoding.ASCII.GetBytes(jwtSettings.JwtKey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = jwtSettings.JwtIssuer,
Audience = jwtSettings.JwtAudience,
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
new Claim(ClaimTypes.Role, user.UserRole.Description)
}),
NotBefore = DateTime.UtcNow,
Expires = DateTime.UtcNow.AddMinutes(120),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return tokenString;
}
Your method in the controller
[Authorize]
[HttpPost("{username}/photos/")]
public IActionResult AddPhoto(String username, [FromForm]IFormFile photo)
{
//add img to static folder
var wwww_root = _webHostEnvironment.WebRootPath;
var path = #"\profiles\" + username + #"\profile_photos\";
var upload_path = wwww_root + path;
var pathForFile = upload_path + photo.FileName;
try
{
using (FileStream fileStream = System.IO.File.Create(pathForFile))
{
photo.CopyTo(fileStream);
fileStream.Flush();
}
}
catch(DirectoryNotFoundException e)
{
return NotFound("This username does not exists");
}
var pathOnServer = "http://" + Request.Host.Value + path + photo.FileName;
var photoImage = _profileService.AddPhoto(username, pathOnServer);
return Ok();
}

Identityserver4 Callback isuue

I'm using identityserver4 with .Net core 3.0 Azure AD authentication for my application.
When user logged in to Microsoft account successfully it should be redirect to angular client application.
Unfortunately I'm getting below error when redirects to the client application.
Below is my OIDC configuration
services.AddAuthentication()
.AddOpenIdConnect("aad", "Demo IdentityServer", options =>
{
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = async ctx =>
{
var message = ctx.ProtocolMessage;
ctx.Properties.Items[OpenIdConnectDefaults.UserstatePropertiesKey] = message.State;
}
};
options.CallbackPath = "/signin-oidc";
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SaveTokens = true;
options.Authority = "xxxxxxxxxxxx",
options.ClientId = "xxxxxxxxxxxx",
options.ResponseType = "code id_token";
options.ClientSecret = "L-soqX=3N8kIU0y1gDbB=Ws_QqAMUw#[";
options.GetClaimsFromUserInfoEndpoint = true;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
//,RoleClaimType = "role"
};
});
return services;
This is my call back method.
public async Task<IActionResult> Callback()
{
// read external identity from the temporary cookie
try
{
var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
if (_logger.IsEnabled(LogLevel.Debug))
{
var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
_logger.LogDebug("External claims: {#claims}", externalClaims);
}
// lookup our user and external provider info
var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result);
if (user == null)
{
user = AutoProvisionUser(provider, providerUserId, claims);
}
var additionalLocalClaims = new List<Claim>();
var localSignInProps = new AuthenticationProperties();
ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps);
var isuser = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.Username,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims
};
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username));
await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, localSignInProps, additionalLocalClaims.ToArray());
await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
// retrieve return URL
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
// check if external login is in the context of an OIDC request
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
//await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.ClientId));
return Redirect(returnUrl);
}
catch (Exception ex)
{
throw;
}
}
Any help would be appreciate in advance.

Core 2.0 API Auth with JWT returns unauthorized

I'm trying to add Token Authentication with JWT to my .Net Core 2.0 app. I have a simple controller that returns a list of users for testing.
[Authorize]
[Route("api/[controller]")]
public class UsersController : Controller
{
...
[HttpGet]
[Route("api/Users/GetUsers")]
public IEnumerable<ApplicationUser> GetUsers()
{
return _userManager.Users;
}
}
I have an API Controller for Token security. It has a login method which returns a Token string result.
[HttpPost(nameof(Login))]
public async Task<IActionResult> Login([FromBody] LoginResource resource)
{
if (resource == null)
return BadRequest("Login resource must be asssigned");
var user = await _userManager.FindByEmailAsync(resource.Email);
if (user == null || (!(await _signInManager.PasswordSignInAsync(user, resource.Password, false, false)).Succeeded))
return BadRequest("Invalid credentials");
string result = GenerateToken(user.UserName, resource.Email);
// Token is created, we can sign out
await _signInManager.SignOutAsync();
return Ok(result);
}
private string GenerateToken(string username, string email)
{
var claims = new Claim[]
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Email, email),
new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds().ToString()),
};
var token = new JwtSecurityToken(
new JwtHeader(new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("the secret that needs to be at least 16 characeters long for HmacSha256")),
SecurityAlgorithms.HmacSha256)),
new JwtPayload(claims));
return new JwtSecurityTokenHandler().WriteToken(token);
}
I have a small console app just for testing the API. When I attempt to Get the Users using the jwt. I receive an immediate "unauthorized". If I remove the "[Authorize]" from the users Controller... success. It appears that my header Authorization is not recognized, but not sure why.
private static async Task<String> GetUsers(String jwt)
{
var url = "https://localhost:44300/";
var apiUrl = $"/api/Users/";
using (var client = new HttpClient() { BaseAddress = new Uri(url) })
{
client.BaseAddress = new Uri(url);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}");
using (var response = await client.GetAsync(apiUrl))
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
return await response.Content.ReadAsStringAsync();
else return null;
}
}
}
I'm basing my attempts on the article here ... some of which might be slightly out of date.
Update - Excerpt of Startup.cs
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Jwt";
options.DefaultChallengeScheme = "Jwt";
}).AddJwtBearer("Jwt", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
//ValidAudience = "the audience you want to validate",
ValidateIssuer = false,
//ValidIssuer = "the isser you want to validate",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("the secret that needs to be at least 16 characeters long for HmacSha256")),
ValidateLifetime = true, //validate the expiration and not before values in the token
ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date
};
});
Configure...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
//app.UseJwtBearerAuthentication(
// new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions
// );
}
Solution:
This line was escaping the token therefore causing it to be invalid when passed in the next request:
var result = await response.Content.ReadAsStringAsync();
Replaced with:
var result = await response.Content.ReadAsAsync<string>();
Note: To use this ext method I had to "install-package Microsoft.AspNet.WebApi.Client"
I used JWT authentication in my one of project. I would like to show my implementation, maybe this will help you. But probably you forget to add UseAuthentication(); into configure method in startup class.
startup.cs
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
var appSettings = Configuration.GetSection("AppSettings");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}
)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = appSettings["JwtAudience"],
ValidateIssuer = true,
ValidIssuer = appSettings["JwtIssuer"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(appSettings["JwtSigningKey"]))
};
});
}
generateToken method
private string GenerateToken(string email)
{
SecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Value.JwtSigningKey));
var token = new JwtSecurityToken(
issuer: _appSettings.Value.JwtIssuer,
audience: _appSettings.Value.JwtAudience,
claims: new[]
{
new Claim(JwtRegisteredClaimNames.UniqueName, email),
new Claim(JwtRegisteredClaimNames.Email, email),
new Claim(JwtRegisteredClaimNames.NameId, Guid.NewGuid().ToString())
},
expires: DateTime.Now.AddMinutes(_appSettings.Value.JwtExpireMinute),
signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
I had created a nuget package NetCore.Jwt to simplify this process recently. I didn't find it worth writing all the code each time you needed a Jwt, when you can handle cookies simply with the function SignInAsync. However, if you prefer the manual way, Celal's answer is a clear and straightforward guide for this process.
Alternatively, you can install NetCore.Jwt and use the following in your startup:
services.AddAuthentication(NetCoreJwtDefaults.SchemeName)
.AddNetCoreJwt(options =>
{
// configure your token options such as secret, expiry, and issuer here
});
In your Login function, you can use the extension function for HttpContext
string token = HttpContext.GenerateBearerToken( new Claim[]
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Email, email),
new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds().ToString()),
});
In your program.cs dont forget to have this code (and in order) :
app.UseAuthentication();
app.UseAuthorization();

Resources