HttpContext Claims doesn't match with JWT.
I Tried Changed nameidentifier's value but context.user.claims value is always same.
The value changes only when I log in with a different account.
Get Claims Code:
public class HttpRequestInterceptor : DefaultHttpRequestInterceptor
{
public override ValueTask OnCreateAsync(
HttpContext context,
IRequestExecutor requestExecutor,
IQueryRequestBuilder requestBuilder,
CancellationToken cancellationToken)
{
string? userId =
context.User.FindFirstValue(ClaimTypes.NameIdentifier);
string? role =
context.User.FindFirstValue(ClaimTypes.Role);
string? companyId =
context.User.FindFirstValue(ClaimTypes.Actor);
if(!string.IsNullOrEmpty(userId) && !string.IsNullOrEmpty(role) && !string.IsNullOrEmpty(companyId))
{
requestBuilder.SetProperty("currentUser",
new CurrentUser(Guid.Parse(userId),Guid.Parse(companyId),role));
}
return base.OnCreateAsync(context,
requestExecutor,
requestBuilder,
cancellationToken);
}
}
Result:
Program.cs :
var jwtSetting = jwtSettingSection.Get<JWTSettings>();
var signingKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(jwtSetting.SecretKey!));
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSetting.ValidIssuer,
ValidAudience = jwtSetting.ValidAudience,
IssuerSigningKey = signingKey,
};
service
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = tokenValidationParameters;
opt.MapInboundClaims = false;
});
service.AddAuthorization();
Please Help me
Related
I am trying to validate token in multi tenant application. In startup(single tenant) earlier used this code for getting configuration data from appsettings.json.
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
But now we need to load configurations data from database. So i have added below code to startup.
services.AddAuthentication("Bearer").AddJwtBearer("Bearer",
options =>
{
options.Authority = "http://localhost:3000";
options.Audience = "fcb78955-1a4a-6666-aa12-fc473b8fd8f6";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new
TokenValidationParameters()
{
ValidateAudience = false
};
});
and this is my token validation code. I have hardcode this for testing purpose. so please ignore it.
public static async Task<ClaimsPrincipal> TokentValidate(string token, string tenantId, string clientId)
{
try
{
var authorityEndpoint = "https://demo.identityserver.io/";
authorityEndpoint = "https://login.microsoftonline.com/" + "9111f39b-e5ed-8899-9c13-0005388e683a" + "/";
var openIdConfigurationEndpoint = $"{authorityEndpoint}.well-known/openid-configuration";
IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(openIdConfigurationEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
clientId = "f0019d64-100a-4990-aa12-fc663b8899f6";
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidIssuer = openIdConfig.Issuer,
ValidAudiences = new[] { clientId },
IssuerSigningKeys = openIdConfig.SigningKeys,
ValidateLifetime = true,
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true
};
SecurityToken validatedToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var user = handler.ValidateToken(token, validationParameters, out validatedToken);
return user;
}
catch (Exception ex)
{
return null;
}
}
But this method not calling. So I have removed startup code. then i am getting this error.
{"Error":"No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action configureOptions)."}
I want to if we are using multi tenant and loading configurations from database what is the correct way. If anyone have an idea please help.
I changed the setting in the TokenValidationParameters (ValidateIssuer,ValidateAudience) from False to True.
What the settings used to be (startup) - everything was working perfectly.
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"])),
ValidateIssuer = false,
ValidateAudience = false,
};
});
Now every call to the API the requires to authorized stopped working.
The new settings - stopped working
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"])),
ValidateIssuer = true,
ValidateAudience = true,
};
});
Every call to the API that needs to be authorized now is getting unauthorized.
This is my token service:
public class TokenService : ITokenService
{
private readonly SymmetricSecurityKey _key;
private readonly UserManager<AppUser> _userManager;
public TokenService(IConfiguration config, UserManager<AppUser> userManager)
{
this._userManager = userManager;
_key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"]));
}
public async Task<string> CreateToken(AppUser user)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.NameId, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName)
};
var roles = await _userManager.GetRolesAsync(user);
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(7),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
Does anyone can please explain to me what causes it so I could learn. And how can I fix it ? I tried many ways but unfortunately, nothing seems to work for me.
Thank you so much.
This is the code at GitHub just in case : https://github.com/davidax0204/HeroMain.git
When you set ValidateIssuer and ValidateAudience to true, you should set value for ValidIssuer and ValidAudience when creating JWT and SecurityTokenDescriptor
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(7),
SigningCredentials = creds,
Audience = "YourAudience",
Issuer = "YourIssuer"
};
And adding same value to TokenValidationParameters properties
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"])),
ValidateIssuer = true,
ValidIssuer = "YourIssuer",
ValidateAudience = true,
ValidAudience = "YourAudience"
};
});
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();
}
I have a asp.net web application using razor pages with PageModel. I need to access a REST web service to get the JWT token
This is my Service configuration within Startup.cs
services.AddIdentity<AppUser, AppUserRole>(cfg =>
{
cfg.User = new UserOptions() { };
cfg.User.RequireUniqueEmail = false;
cfg.SignIn.RequireConfirmedEmail = false;
})
.AddUserManager<AppUserManager<AppUser>>()
.AddUserStore<AppUserStore>()
.AddRoleStore<AppRoleStore>()
.AddDefaultTokenProviders();
services.Configure<TokenOptions>(Configuration.GetSection("TokenConf"));
var tokenConf = Configuration.GetSection("TokenConf").Get<TokenConf>();
services.Configure<AppConf>(Configuration.GetSection("AppConf"));
var appConf = Configuration.GetSection("AppConf").Get<AppConf>();
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidIssuer = tokenConf.Issuer,
ValidAudience = tokenConf.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(appConf.JWTSecretKey))
};
})
.AddCookie();
services.AddSingleton<IAuthenticationServiceProxy, AuthenticationServiceProxy>();
services.AddHttpContextAccessor();
services.AddMemoryCache();//alternatively we can use services.AddDistributedMemoryCache() and IDistributedCache cache
services.AddSession(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
services.AddOptions();
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Index");
options.Conventions.AuthorizePage("/Privacy");
options.Conventions.AllowAnonymousToPage("/Account/Login");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
This is my login code within LoginModel : PageModel
public async Task<IActionResult> OnPostAsync(string returnUrl, string handler)
{
if (!ModelState.IsValid)
{
return Page();
}
var appUser = new AppUser() { UserName = UserLogin.Username };
var result = await _signInMgr.PasswordSignInAsync(appUser, UserLogin.Password, false, false);//_signInMgr.PasswordSignInAsync(UserLogin.Username, UserLogin.Password, false, false);
if (result.Succeeded)
{
var userTokenData = _authServPrx.GetTokenData(_appConf.Value.CslLink, UserLogin.Username, UserLogin.Password);
JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(userTokenData.Token);
var jwt = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
new OkObjectResult(jwt);
}
else
return BadRequest("Bad username or password"); //TODO: better return
return Page();
}
It doesn't work, the redirect doesn't happen, but somehow the authentication cookie does get set. So if I then manually go to /Index
I reach
public void OnGet()
{
var accessToken = Request.Headers["Authorization"];
}
But accessToken is empty.
I just want to redirect and to be able to access my token somehow.
Am I doing something wrong?
That depends on where you want to store the token ,session, cache.... For example, you can pass with query string :
if (result.Succeeded)
{
var userTokenData = _authServPrx.GetTokenData(_appConf.Value.CslLink, UserLogin.Username, UserLogin.Password);
JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(userTokenData.Token);
var jwt = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
return LocalRedirect(returnUrl+"?token="+jwt);
}
And get token in page :
public void OnGet(string token)
{
}
I'm working on .net core api 2.1, I have implemented JWT token authentication, I want jwt token to expire after given time, but it is not expiring. Token still validation even after expiry time.
Startup.cs code:
// configure jwt authentication
var jwtSettings = jwtSettingsSection.Get<JWTSettings>();
var key = Encoding.ASCII.GetBytes(jwtSettings.SECRET);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ClockSkew = TimeSpan.Zero
};
});
services.Configure<IISOptions>(options =>
{
options.AutomaticAuthentication = true;
//options.ForwardClientCertificate = true;
});
SignIn api code to create token on sign in:
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtSettings.SECRET);
var currentTime = DateTime.Now;
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.Now.AddMinutes(2),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
rs.Token = tokenString;
Auth filter to validate token:
public void OnAuthorization(AuthorizationFilterContext filterContext)
{
if (!ValidateToken(filterContext.HttpContext.Request.Headers["TOKEN"]))
{
filterContext.Result = new UnauthorizedResult();
}
}
private bool ValidateToken(string authToken)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = GetValidationParameters();
SecurityToken validatedToken;
IPrincipal principal = tokenHandler.ValidateToken(authToken, validationParameters, out validatedToken);
return true;
}
catch(Exception ex)
{
return false;
}
}
private TokenValidationParameters GetValidationParameters()
{
return new TokenValidationParameters()
{
ValidateLifetime = false, // Because there is expiration in the generated token
ValidateAudience = false, // Because there is no audiance in the generated token
ValidateIssuer = false, // Because there is no issuer in the generated token
//ValidIssuer = _appSettings.ValidIssuer,
//ValidAudience = _appSettings.ValidAudience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretkey)) // The same key as the one that generate the token
};
}
What can be the issue?