Pattern for get user's informations in all my services from JWT token - asp.net-core

I have an api with many controllers. One of these controllers is the authentication one. It use to get a JWT token and call others paths, from this controller or another.
My problem is, for any paths, I need to get the user id in the JWT token, and ask to the database to get informations about user, like check if the user exist or datas linked to him. So in each method, I have to call a specific method to retrieve informations.
In fact, the authentication layer is only used in order to check if the JWT Token is valid. I don't know if is it possible to add some logic in this layer. And how.
Can I implement any pattern in order to retrieve automatically user's information ?
I thought about singleton, but I'm not sure about the scope of the object. The goal is to stay in the request scope.
I thought to create an User Service, but I think it is not a good way because services are about treatment, not keep datas in these.
I thought to implement a custom middleware, but I'm not sure about the way to do it.
The objective is to implement all the user logic in the same place, and each service can call it in order to deal with it :
Request (with JWT Token) --> Controller --> Service --> Call the object with all user's stuff and logic and treatment
or
Request (with JWT token) --> middleware (get all user's informations) --> Controller --> Call the object created in middleware with all user's stuff
For information, there are my login method and the way I create the JWT token :
public async Task<ServiceResponse<UserLoginResponseDto>> Login(ServiceRequest<UserLoginRequestDto> request)
{
ServiceResponse<UserLoginResponseDto> response = new ServiceResponse<UserLoginResponseDto>();
User user = await _context.Users.FirstOrDefaultAsync(x => x.Username.ToLower().Equals(request.Data.Username.ToLower()));
if (user == null)
{
response.Success = false;
response.Message = "User not found";
}
else if (user.IsLocked)
{
response.Success = false;
response.Message = "User locked";
}
else if (!user.IsActivated)
{
response.Success = false;
response.Message = "User not activated";
}
else if (!VerifyPasswordHash(request.Data.Password, user.PasswordHash, user.PasswordSalt))
{
response.Success = false;
response.Message = "Wrong password";
}
else
{
response.Data = _mapper.Map<UserLoginResponseDto>(user);
response.Data.JWtToken = CreateToken(user);
}
return response;
}
private string CreateToken(User user)
{
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
};
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.GetSection("AppSettings:Token").Value));
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = creds
};
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}

Related

ASP.NET Core 3.1 - AddJwtBearer, OnTokenValidated event not respecting custom message on Fail

I have a scenario where I need to recreate the principal if a bearer token is provided in the request. For this I use the OnTokenValidated event to execute some custom logic (if bearer is valid). I check if the user email is verified, if so I add custom claims to the user identity which I can then access later on during the same request and make use of the authorisation attributes on controllers and actions.
However I'm trying to return a custom message if the email is not verified, but I keep getting "Unauthorised" back, even though this code is being hit and using the preferred message.
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = customDomain;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudiences = audiences,
ValidateIssuer = true,
ValidIssuers = issuers
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = async (context) =>
{
var user = context.Principal;
//Check if already restored during current request
if (user.GetDefaultUserPrincipal() == null)
{
var securityManager = context.HttpContext.RequestServices.GetRequiredService<ISecurityManager>();
var authResponse = await securityManager.AuthenticateMarketplaceFromBearerRequestAsync(user);
if(!authResponse.IsAuthenticated)
{
context.Fail(authResponse.Message);
}
}
}
};
});
Am I missing something here? I've also tried throwing an exception and handling that response in the AuthenticationFailed event, but I get the same thing.
Alternatively I'm playing with the idea of creating a custom policy to do this check as long I can still return a custom response message.
In order to display your custom message when failing the authentication, you can write it to the response of the AuthenticationFailedContext context object.
...
if (!authResponse.IsAuthenticated){
// set the content-type of the response
context.Response.ContentType = "application/json";
// prepare your custom data
var data = new { MyCustomMessage = authResponse.Message };
// user serializer to form your data to string. Here I used Newtonsoft.Json
var jsonResult = JsonConvert.SerializeObject(data);
// Write the jsonresult to the response. Make sure this returns a Task
context.Response.WriteAsync(jsonResult);
}
...

Implementing refresh token in Web API Core 3.1

I need to implement refresh token in my Web API. I found this article, so I need to elaborate further with implementation of refresh token. As well, I found some tutorials how to do it, but I do not have idea how to start in my use case. This is the action in my Auth controller responsible for login, and generating token:
[HttpPost]
[Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel model)
{
var user = await userManager.FindByNameAsync(model.Username);
if (user != null &&
await userManager.CheckPasswordAsync(user, model.Password) &&
await userManager.IsEmailConfirmedAsync(user))
{
var userRoles = await userManager.GetRolesAsync(user);
var authClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
foreach (var userRole in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, userRole));
}
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));
var token = new JwtSecurityToken(
issuer: _configuration["JWT:ValidIssuer"], // The first parameter is a simple string representing the name of the webserver that issues the token
audience: _configuration["JWT:ValidAudience"], // The second parameter is a string value representing valid recipients
expires: DateTime.UtcNow.AddSeconds(15), // DateTime object that represents the date and time after which the token expires
claims: authClaims, // a list of user roles, for example, the user can be an admin, manager or author
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
dateTimeNow = DateTime.UtcNow,
expiration = token.ValidTo
});
}
return Unauthorized();
}
Any idea how and where to start?
I think you should try using IdentityServer4. Everything you need for OAuth token authentication is already implemented there. You can enable refresh tokens with couple of simple configurations.
Here for more information: https://identityserver4.readthedocs.io/en/latest/
And more about the refresh tokens: https://identityserver4.readthedocs.io/en/latest/topics/refresh_tokens.html
IdentityServer is part of the .NET Foundataion: https://dotnetfoundation.org/projects/identityserver
You could create a refresh token entity or add a property in the Login model to store the refresh token, and set it's expired time. After getting the refresh token, you could store it in the cookie. Try to search "asp.net core JWT refresh token authentication" online, you will find lots of tutorials, such as:
ASP.NET Core 3.1 API - JWT Authentication with Refresh Tokens
Implement JWT In .NET Core API
Using Refresh Tokens in ASP.NET Core Authentication
You could implement a try/catch on your method that calls the remote API, and as soon as it fails due to the expired token, you can initiate a renewal of the refresh token and try again.
Like this:
public async Task<TResponse> ExecuteWithRetryAsync<TResponse>(Func<Task<TResponse>> webApiCallMethod)
{
var tryForceRefreshToken = false;
var attemptsCounter = 1;
while (true)
{
if (tryForceRefreshToken)
{
var success = await TryAuthWithRefreshTokenAsync();
}
try
{
attemptsCounter++;
var response = await webApiCallMethod.Invoke();
return response;
}
catch (HttpRequestException)
{
if (attemptsCounter > 2)
{
throw;
}
tryForceRefreshToken = true;
}
catch (Exception)
{
throw;
}
}
}
And as soon as it fails you attempt to execute a TryAuthWithRefreshTokenAsync a method for attempting to refresh the token by using your expired bearer token, as this:
private async Task<bool> TryAuthWithRefreshTokenAsync()
{
try
{
//Tenta executar o refreshtoken apenas da primeira thread que solicitou...
//Para as demais threads, faz com que elas aguardem pela renovacao do token.
if (Interlocked.CompareExchange(ref _refreshTokenEntered, 1, 0) == 0)
{
Console.WriteLine("Refresh Token Renewing...");
//tenta renovar
var authResponse = await AuthWithRefreshTokenAsync();
Interlocked.Exchange(ref _refreshTokenEntered, 0);
Console.WriteLine("Refresh Token Renewed");
return authResponse.Success;
}
else
{
Console.WriteLine("Refresh Token Renewal is Waiting...");
while (_refreshTokenEntered == 1)
{
await Task.Delay(100);
}
//Faz as outras threads aguardarem até que o token seja renovado no bloco anterior
Console.WriteLine("Refresh Token Renewal done!");
return true;
}
}
catch (Exception)
{
Interlocked.Exchange(ref _refreshTokenEntered, 0);
throw;
}
}
After a successfull bearer token renewal the method ExecuteWithRetryAsync can try again and call your remote web api.
For more please see here.

Where to store JWT Token in .net core web api?

I am using web api for accessing data and I want to authenticate and authorize web api.For that I am using JWT token authentication. But I have no idea where should I store access tokens?
What I want to do?
1)After login store the token
2)if user want to access any method of web api, check the token is valid for this user,if valid then give access.
I know two ways
1)using cookies
2)sql server database
which one is the better way to store tokens from above?
Alternatively, if you just wanted to authenticate using JWT the implementation would be slightly different
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var user = context.Principal.Identity.Name;
//Grab the http context user and validate the things you need to
//if you are not satisfied with the validation fail the request using the below commented code
//context.Fail("Unauthorized");
//otherwise succeed the request
return Task.CompletedTask;
}
};
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey("MyVeryStrongKeyHiddenFromAnyone"),
ValidateIssuer = false,
ValidateAudience = false
};
});
still applying use authentication before use MVC.
[Please note these are very simplified examples and you may need to tighten your security more and implement best practices such as using strong keys, loading configs perhaps from the environment etc]
Then the actual authentication action, say perhaps in AuthenticationController would be something like
[Route("api/[controller]")]
[Authorize]
public class AuthenticationController : Controller
{
[HttpPost("authenticate")]
[AllowAnonymous]
public async Task<IActionResult> AuthenticateAsync([FromBody]LoginRequest loginRequest)
{
//LoginRequest may have any number of fields expected .i.e. username and password
//validate user credentials and if they fail return
//return Unauthorized();
var claimsIdentity = new ClaimsIdentity(new Claim[]
{
//add relevant user claims if any
}, "Cookies");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await Request.HttpContext.SignInAsync("Cookies", claimsPrincipal);
return Ok();
}
}
in this instance I'm using cookies so I'm returning an HTTP result with Set Cookie. If I was using JWT, I'd return something like
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]LoginRequest loginRequest)
{
//validate user credentials and if they validation failed return a similar response to below
//return NotFound();
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("MySecurelyInjectedAsymKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
//add my users claims etc
}),
Expires = DateTime.UtcNow.AddDays(1),//configure your token lifespan and needed
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey("MyVerySecureSecreteKey"), SecurityAlgorithms.HmacSha256Signature),
Issuer = "YourOrganizationOrUniqueKey",
IssuedAt = DateTime.UtcNow
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
var cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(4);//you can set this to a suitable timeframe for your situation
cookieOptions.Domain = Request.Host.Value;
cookieOptions.Path = "/";
Response.Cookies.Append("jwt", tokenString, cookieOptions);
return Ok();
}
I'm not familiar with storing your users tokens on your back end app, I'll quickly check how does that work however if you are using dotnet core to authenticate with either cookies or with jwt, from my understanding and experience you need not store anything on your side.
If you are using cookies then you just need to to configure middleware to validate the validity of a cookie if it comes present in the users / consumer's headers and if not available or has expired or can't resolve it, you simply reject the request and the user won't even hit any of your protected Controllers and actions. Here's a very simplified approach with cookies.(I'm still in Development with it and haven't tested in production but it works perfectly fine locally for now using JS client and Postman)
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "yourCookieName";
options.Cookie.SameSite = SameSiteMode.None;//its recommended but you can set it to any of the other 3 depending on your reqirements
options.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents
{
OnRedirectToLogin = redirectContext =>//this will be called if an unauthorized connection comes and you can do something similar to this or more
{
redirectContext.HttpContext.Response.StatusCode = 401;
return Task.CompletedTask;
},
OnValidatePrincipal = context => //if a call comes with a valid cookie, you can use this to do validations. in there you have access to the request and http context so you should have enough to work with
{
var userPrincipal = context.Principal;//I'm not doing anything with this right now but I could for instance validate if the user has the right privileges like claims etc
return Task.CompletedTask;
}
};
});
Obviously this would be placed or called in the ConfigureServices method of your startup to register authentication
and then in your Configure method of your Startup, you'd hookup Authentication like
app.UseAuthentication();
before
app.UseMvc()

ASP.NET Core 1.0. Bearer Token, cannot access custom claims

I'm trying to setup Bearer authentication for an SPA using ASP.NET Core 1.0. I've almost got it working for JwtToken with OpenIdConnect Server but have an issue that my custom claims are not returned with the token.
My Startup.cs logic for authentication is the following:
private void ConfigureAuthentication(IApplicationBuilder app)
{
app.UseJwtBearerAuthentication(options =>
{
options.AutomaticAuthenticate = true;
options.Authority = "http://localhost:53844";
options.Audience = "http://localhost:53844";
options.RequireHttpsMetadata = false;
});
app.UseOpenIdConnectServer(options =>
{
options.TokenEndpointPath = "/api/v1/token";
options.AllowInsecureHttp = true;
options.AuthorizationEndpointPath = PathString.Empty;
options.Provider = new OpenIdConnectServerProvider
{
OnValidateClientAuthentication = context =>
{
context.Skipped();
return Task.FromResult<Object>(null);
},
OnGrantResourceOwnerCredentials = async context =>
{
var usersService = app.ApplicationServices.GetService<IUsersService>();
User user = usersService.getUser(context.Username, context.Password);
var identity = new ClaimsIdentity(new List<Claim>(), OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Name, user.Id.ToString()));
identity.AddClaim(new Claim("myclaim", "4815162342"));
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
ticket.SetResources(new[] { "http://localhost:53844" });
ticket.SetAudiences(new [] {"http://localhost:53844"});
ticket.SetScopes(new [] {"email", "offline_access" });
context.Validated(ticket);
}
};
});
}
Both access_token and refresh_token are generating succesfully and when passing access_token in Authorization header system treats request as authorized.
The only issue is that all claims except NameIdentifier are not passed.
I use the following code to receive my claims for authenticated request:
public class WebUserContext : IUserContext
{
private readonly IHttpContextAccessor contextAccessor;
public WebUserContext(IHttpContextAccessor contextAccessor)
{
this.contextAccessor = contextAccessor;
}
public long UserId
{
get
{
ClaimsIdentity identity = Principal?.Identity as ClaimsIdentity;
if (identity == null)
{
return -1;
}
Claim claim = identity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name); // There is no such claim in claims collection
return long.Parse(claim.Value);
}
}
private ClaimsPrincipal Principal => contextAccessor.HttpContext.User as ClaimsPrincipal;
}
What can be the reason my claims are not passed or extracted from the token?
What can be the reason my claims are not passed or extracted from the token?
Security.
Unlike OAuthAuthorizationServerMiddleware, ASOS doesn't assume access tokens are always consumed by your own resource servers (though I agree it's a common scenario) and refuses to serialize claims that don't explicitly specify a "destination" to avoid leaking confidential data to unauthorized parties.
With JWT being the default format in ASOS beta4 (but not in the next beta), you must also keep in mind that even client applications (or users) can read your access tokens.
For this reason, you must explicitly attach a "destination" to your claims:
identity.AddClaim(ClaimTypes.Name, "Pinpoint", destination: "id_token token");
Specify id_token to serialize the claim in the identity token, token to serialize it in the access token or both to serialize it in both tokens (there's no equivalent for authorization codes or refresh tokens as they are always encrypted and only readable by the authorization server itself)

Thinktecture IdentityServer v3 LogOut for Implicit flow

How do I get the id_token for the implicit token to pass in the id_token hint for logout for implicit flow or is there another way? I have the end point /connect/endsession?
id_token_hint=
Not sure how I get the id_token from the implict flow all I get is a access_token and expiration. Is there a setting in IdSvr?
There's three components to this.
First ensure you're requesting an id_token from Identity Server when you're configuring the OIDC authentication in your Startup.cs (as mentioned by #leastprivilege above):
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44301/",
...
ResponseType = "id_token token", //(Here's where we request id_token!)
Secondly, using the OIDC notifications & after the security token is validated you add the id_token to your user's claims:
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var nid = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
Constants.ClaimTypes.GivenName,
Constants.ClaimTypes.Role);
// get userinfo data
var userInfoClient = new UserInfoClient(
new Uri(n.Options.Authority + "/" + Constants.RoutePaths.Oidc.UserInfo),
n.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
userInfo.Claims.ToList().ForEach(ui => nid.AddClaim(new Claim(ui.Item1, ui.Item2)));
// keep the id_token for logout (**This bit**)
nid.AddClaim(new Claim(Constants.TokenTypes.IdentityToken, n.ProtocolMessage.IdToken));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
},
Finally, on the redirect for signout (also a notification event) you add the id_token to the Protocol Message:
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst(Constants.TokenTypes.IdentityToken);
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
You'll also need to ensure you setup the PostLogoutRedirectUris on the client within Identity Server:
new Client
{
Enabled = true,
ClientName = "(MVC) Web App",
ClientId = "mvc",
Flow = Flows.Implicit,
PostLogoutRedirectUris = new List<string>
{
"https://localhost:44300/" //(** The client's Url**)
}
}
That will ensure you give the user an option to return to the authorised client when they log out :)
All of this is pretty much as per the MVC Sample at https://identityserver.github.io/Documentation/docsv2/overview/mvcGettingStarted.html
Bit more than you asked for but hopefully that helps anyone else who's trying to figure it out too :)
To get an id_token, you have to ask for it. Use response_type=id_token token
Have you tried this?
ASP.Net Identity Logout
It should create the id token hint automatically