var identity = new GenericIdentity(user.Username, "Token");
var claims = new List<Claim>();
claims.AddRange(identity.Claims);
foreach (RoleType r in roles)
{
claims.Add(new Claim("role", r.ToString()));
}
claims.Add(new Claim(JwtRegisteredClaimNames.Jti, tokenUid));
claims.Add(new Claim(JwtRegisteredClaimNames.Iat,
ServiceHelper.ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64));
var jwt = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
notBefore: _jwtOptions.NotBefore,
expires: _jwtOptions.Expiration,
signingCredentials: _jwtOptions.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var authToken = new AuthToken();
authToken.TokenValue = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(encodedJwt));
authToken.ExpirationInSeconds = (int)_jwtOptions.ValidFor.TotalSeconds;
return authToken;
The above code is giving me the token taking user credentials as input.
Whenever I try to access the below code using Postman, it is giving me Bearer error ="invalid_token" and 401 unauthorized.
[HttpPost("addStudent")]
[Authorize(Roles = "Director,Student")]
public IActionResult Post([FromBody]Student studentFields)
{
if (s == null)
{
var student = _studentService.CreateStudent(studentFields);
return createResponse(201, new
{
studentInfo = student
});
}
_logger.LogInformation("Student already added:{0}", s);
return createErrorResponse("student already added", 404);
}
In the header, I am giving Authorization = Bearer + token(token generated from above API).
I don't understand why it is giving me an invalid bearer token and 401.
I have seen a lot of examples, whenever a token has been given in the header, the client should able to access the respective API.
In IdentityServer had to add claim "aud" to the jwt Token. In Order to do that enable option.audience that matches ApiResource under .AddJwtBearer("Bearer", options => options.Audience="invoice" and set ApiResource
Reference Link https://identityserver4.readthedocs.io/en/latest/topics/resources.html#refresources
public static readonly IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("invoice", "Invoice API")
{
Scopes = { "invoice.read", "invoice.pay", "manage" }
}
};
}
Related
my startup.cs (asp.net core 5.0)
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication (options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = "https://www.yogihosting.com",
ValidIssuer = "https://www.yogihosting.com",
ClockSkew = TimeSpan.Zero,// It forces tokens to expire exactly at token expiration time instead of 5 minutes later
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MynameisJamesBond007"))
};
});
}
I am trying to invoke http://localhost:31254/Reservation which returns the list of flight reservation lists which I am tring to call from CallAPIController.cs
ReservationController.cs
[Authorize]
public IEnumerable<Reservation> Index()
{
return CreateDummyReservations();
}
[HttpGet]
public IEnumerable<Reservation> Get() => CreateDummyReservations();
public List<Reservation> CreateDummyReservations()
{
List<Reservation> rList = new List<Reservation> {
new Reservation { Id=1, Name = "Ankit", StartLocation = "New York", EndLocation="Beijing" },
new Reservation { Id=2, Name = "Bobby", StartLocation = "New Jersey", EndLocation="Boston" },
new Reservation { Id=3, Name = "Jacky", StartLocation = "London", EndLocation="Paris" }
};
return rList;
}
CallAPIController.cs
//entry point of the controller
public async Task<IActionResult> Index(string message)
{
ViewBag.Message = message;
var accessToken = GenerateJSONWebToken(); //generating the token
SetJWTCookie(accessToken); //setting the cookie
List<Reservation> list = await FlightReservation();
return RedirectToAction("Home");
}
private string GenerateJSONWebToken()
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MynameisJamesBond007"));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "https://www.yogihosting.com",
audience: "https://www.yogihosting.com",
expires: DateTime.Now.AddHours(3),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
private void SetJWTCookie(string token)
{
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Expires = DateTime.UtcNow.AddHours(3),
};
Response.Cookies.Append("jwtCookie", token, cookieOptions);
}
public async Task<List<Reservation>> FlightReservation()
{
var jwt = Request.Cookies["jwtCookie"];
List<Reservation> reservationList = new List<Reservation>();
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
//calling the reservation controller asynchronously but returning
unauthorised.
using (var response = await httpClient.GetAsync("http://localhost:31254/Reservation")) // change API URL to yours
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
string apiResponse = await response.Content.ReadAsStringAsync();
reservationList = JsonConvert.DeserializeObject<List<Reservation>>(apiResponse);
}
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
//return RedirectToAction("Index", new { message = "Please Login again" });
}
}
}
return reservationList;
}
When I call the reservation controller I get 401 unauthorised error, though. If i remove authorize attribute it works. The point is it is not recognising the JWT token or validating it properly. Am I missing anything?
Your problem is in this line :
SetJWTCookie(accessToken);
List<Reservation> list = await FlightReservation();
When you set the cookie, the response must be received in the browser to set the cookie in the browser to be sent in subsequent requests, but the response has not been sent to the browser yet and you call this method
await FlightReservation();
This method requests a cookie that has not yet been set in the browser and has not been sent to this request, so the received token is empty here
var jwt = Request.Cookies["jwtCookie"]; //cookie is null
And the unauthorized error returns, but if this request ends, there will be no problem in subsequent requests because the cookie is set in the browser. So with your code it will always return unauthoruzed in first Request.
But if all requests fail, see if you have set up UseAuthentication Middleware or not
app.UseRouting();
app.UseAuthentication(); //here
app.UseAuthorization();
You set the cookie in your SetJWTCookie(string token) method:
Response.Cookies.Append("jwtCookie", token, cookieOptions)
And you try to get the cookie in the FlightReservation() method:
var jwt = Request.Cookies["jwtCookie"];
your Request dosn't contain jwtCookie. so you can't get the value of your jwt
token
Just try as below:
[HttpPost]
public async Task<IActionResult> Authenticate(UserModel someuser)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MynameisJamesBond007"));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "https://www.yogihosting.com",
audience: "https://www.yogihosting.com",
expires: DateTime.Now.AddHours(3),
signingCredentials: credentials
);
string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken);
var response = await httpClient.GetAsync(url); // change API URL to yours
}
return Content(jwtToken);
}
Result:
I am building a .Net core 6 mvc website which will interact with an API built by an external party. Amongst other things, the user authentication is handled by the API. The API responds with a JWT bearer token once user is authenticated and I need to tie that in to my website to Authorize controller methods.
At this point I call the API and successfully receive the token as expected, however after days of struggling to get [Authorize] to work in the controllers with the token, I am completely lost and hoping for some guidance.
After scrapping multiple iterations of code, this what I currently have.... (excuse the mess)
public async Task<TokenResponse> LoginAsync(string email, string password)
{
var userLogin = new UserLogin
{
Email = email,
Password = password
};
string encoded = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(email + ":" + password));
var client = new RestClient("api location");
var request = new RestRequest();
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Basic " + encoded);
var response = await client.GetAsync(request);
var result = JsonConvert.DeserializeObject<TokenResponse>(response.Content);
return result;
}
public async Task<IActionResult> LoginPostAsync(LoginViewModel viewModel)
{
var tokenResponse = await _userManagementService
.LoginAsync(viewModel.Email, viewModel.Password);
if (!string.IsNullOrEmpty(tokenResponse.access_token))
{
var handler = new JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(tokenResponse.access_token);
var jti = jwtSecurityToken.Claims.First(claim => claim.Type == "jti").Value;
var account_type = jwtSecurityToken.Claims.First(claim => claim.Type == "account_type").Value;
var userId = jwtSecurityToken.Claims.First(claim => claim.Type == "user_id").Value;
var email = jwtSecurityToken.Claims.First(claim => claim.Type == "email").Value;
var iss = jwtSecurityToken.Claims.First(claim => claim.Type == "iss").Value;
string[] userRoles = { "admin", "candidate",};
HttpContext context = new DefaultHttpContext();
var accessToken = tokenResponse.access_token;
//var userClaims = new List<Claim>()
// {
// new Claim("email", email),
// new Claim("account_type", account_type),
// new Claim("jti", jti),
// };
//var userIdentity = new ClaimsIdentity(userClaims, "User Identity");
//var userPrincipal = new ClaimsPrincipal(new[] { userIdentity });
//context.SignInAsync(userPrincipal);
//Response.Cookies.Append(
// Constants.XAccessToken,
// tokenResponse.access_token, new CookieOptions
// {
// Expires = DateTimeOffset.UtcNow.AddMinutes(1),
// HttpOnly = true,
// SameSite = SameSiteMode.Strict
// });
//return new AuthenticateResponse(user, token);
SetJWTCookie(accessToken);
return RedirectToAction("index", "Home", new { area = "CandidateDashboard" });
}
return Unauthorized();
}
Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(config =>
{
config.SaveToken = true;
config.RequireHttpsMetadata = false;
config.TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = false,
ValidateIssuer = true,
ValidIssuer = "issue data",
ValidateIssuerSigningKey = false,
};
config.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["Bearer"];
return Task.CompletedTask;
}
};
});
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Index()
{
return View();
}
This is the what I see in dev console.
--bearer error="invalid_token", error_description="the signature key was not found"
Payload from Bearer
{
"iss": "data here",
"exp": 1647323406,
"nbf": 1647319746,
"iat": 1647319806,
"jti": "e8f297d3-blah blah",
"account_type": "candidate",
"user_id": "2342342342",
"email": "email#email.com"
}
The core problem is that AddJwtBearer by default only trusts token issued by someone it trusts (the issuer) because it needs to verify the signature of the token. You of course want to verify it so a hacker doesn't send fake/forged tokens to your API.
So either you need to add that
.AddJwtBearer(opt =>
{
opt.Authority = "https://issuer.com"
In this way, AddJwtBearer will download the public signing key automatically for you.
Or you need to add the public signing key manually to AddJwtBearer.
see https://devblogs.microsoft.com/dotnet/jwt-validation-and-authorization-in-asp-net-core/
I have a Web Api project in .net core 3.1 and I have added JwT authentication.
The authentication and authorization work very well, but I need to get the UserId in every request. When the method is decorated with Authorize attribute, this works well.
[HttpGet]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IEnumerable<WeatherForecast> Get()
{
string user = User.Identity.Name; //Get a value
//Do something
}
However I have some method which authentication is not required, but if an authenticated user make a request, I would like to get the userId, but in this case, user.Identity.Name is always null.
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
string user = User.Identity.Name; //null
//Do somwthing
}
My configuration in statur file is:
private void ConfigureJwt(IServiceCollection services)
{
//Add Auth scheme
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
AuthSettings authSettings = Configuration.GetSection("AuthSettings").Get<AuthSettings>();
JwtIssuerOptions jwtIssuerOptions = Configuration.GetSection("JwtIssuerOptions").Get<JwtIssuerOptions>();
services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtIssuerOptions.Issuer,
ValidAudience = jwtIssuerOptions.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey))
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
//When method is no decorated with Authorize, it not working
var userId = int.Parse(context.Principal.Identity.Name);
return System.Threading.Tasks.Task.CompletedTask;
}
};
});
services.AddTransient<ITokenService, TokenService>(x =>
{
return new TokenService(Configuration);
});
}
TokenService class:
public class TokenService : ITokenService
{
IConfiguration configuration = null;
AuthSettings authSettings = null;
public TokenService(IConfiguration _configuration)
{
configuration = _configuration;
authSettings = configuration.GetSection("AuthSettings").Get<AuthSettings>();
}
public string GenerateAccessToken(IEnumerable<Claim> claims, ref JwtIssuerOptions jwtIssuerOptions)
{
//var authSettings = configuration.GetSection(nameof(AuthSettings));
//var authSettings = configuration.GetSection("EmailSettings").Get<AuthSettings>();
jwtIssuerOptions = configuration.GetSection("JwtIssuerOptions").Get<JwtIssuerOptions>();
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokeOptions = new JwtSecurityToken (
issuer: jwtIssuerOptions.Issuer,
audience: jwtIssuerOptions.Audience,
claims: claims,
expires: jwtIssuerOptions.Expiration,
//expires: DateTime.Now.AddMinutes(5),
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
return tokenString;
}
public string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
public ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
{
TokenValidationParameters tokenValidationParameters = GetValidationParameters();
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;
}
private TokenValidationParameters GetValidationParameters()
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false, //you might want to validate the audience and issuer depending on your use case
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey)),
ValidateLifetime = false //here we are saying that we don't care about the token's expiration date
};
return tokenValidationParameters;
}
}
AuthController
[HttpPost, Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel loginModel)
{
if (loginModel == null)
return BadRequest("Invalid client request");
var sessionInfo = await userBo.LoginUser(loginModel);
if (sessionInfo == null)
return Unauthorized();
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, sessionInfo.User.BusinessEntityId.ToString()),
new Claim(ClaimTypes.Role, sessionInfo.User.RoleCode)
};
JwtIssuerOptions tokeOptions = null;
var accessToken = tokenService.GenerateAccessToken(claims, ref tokeOptions);
var refreshToken = tokenService.GenerateRefreshToken();
await tokenBo.SaveToken(
new Token()
{
BusinessEntityId = sessionInfo.Person.BusinessEntityId,
RefreshToken = refreshToken,
RefreshTokenExpiryTime = tokeOptions.Expiration
}
);
sessionInfo.TokenInfo = new TokenInfo()
{
AccessToken = accessToken,
RefreshToken = refreshToken
};
return Ok(sessionInfo);
}
}
Thank you for your help!
As far as I know, if the controller doesn't need authorize, it will not add the user information into pipeline claims, so the user name is always null.
To solve this issue, I suggest you could try to add a custom middleware to check if the request contains the Authorization header. If it contains you could get the username and add it into http context item.
Then you could directly get the username in the api controller instead of getting it from User.Identity.Name.
More details, you could refer to below codes:
Add below middleware into startup.cs Configure method:
app.Use(async (context, next) =>
{
// you could get from token or get from session.
string token = context.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(token))
{
var tok = token.Replace("Bearer ", "");
var jwttoken = new JwtSecurityTokenHandler().ReadJwtToken(tok);
var jti = jwttoken.Claims.First(claim => claim.Type == ClaimTypes.Name).Value;
context.Items.Add("Username", jti);
}
await next();
});
Controller get the username:
object value;
ControllerContext.HttpContext.Items.TryGetValue("Username", out value);
var username = value.ToString();
Result:
After changing an application from using cookie-based authentication to using JWT I ran into this problem. You can work around it — sort of — by creating an authorization handler with no requirements thus allowing anonymous users access. The ASP.NET pipeline doesn't know which requirements will be required so it will provide the credentials of the user if they are present in the request. The end result is that anonymous users are allowed but if credentials are provided they will be available.
The trivial requirement:
class RequireNothing : IAuthorizationRequirement { }
And the handler:
class RequireNothingHandler : AuthorizationHandler<RequireNothing>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequireNothing requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
If the request contains credentials they will become available in the User object but the requirement also allow anonymous users access.
To use the requirement you can create a policy (and also add the handler to the DI container):
services
.AddAuthorization(options => options
.AddPolicy("AlsoAllowAnonymous", policy => policy
.AddRequirements(new RequireNothing())))
.AddSingleton<IAuthorizationHandler, RequireNothingHandler>();
To combine authenticated and anonymous access you decorate the action or controller with the attribute:
[Authorize(Policy = "AlsoAllowAnonymous")]
Unfortunately, this might not work so well. If you are using a long-lived JWT refresh tokens and short-lived access tokens that are refreshed when a 401 challenge is received there will be no challenge after the access token expires and the user will access the end-point anonymously possibly resulting in a degraded user experience even though the user has authenticated and has a refresh token to prove that.
This problem is not unique to using an authorization handler and you get more control by providing two different end-points: one for anonymous users and another one for authenticated users. You need some extra logic on the client side to select the correct API for things to work out right though.
I'm using IdentityServer4 for user authentication and authorization in my asp.net core web api.I use this api in android application.My users Signup and Login with username and password with no problem .And here is my access token that I got from connect/token endpoint
{
"alg": "RS512",
"typ": "at+jwt"
}
{
"nbf": 1600324303,
"exp": 1631860303,
"iss": "https://myIdentityServerApi.com",
"aud": [
"IdentityServerApi",
"MyAPI1"
],
"client_id": "MyApp1",
"sub": "521d198c-3657-488e-997e-3e50d756b353",
"auth_time": 1600324302,
"idp": "local",
"role": "Admin",
"name": "myusername",
"scope": [
"openid",
"IdentityServerApi",
"MyAPI1"
],
"amr": [
"pwd"
]
}
Now in my new android application I want users signup and login with phone number and sms activation.
When User send the ActivationCode I should send him access token.But how can I get token from token endpoint without username and password?
In below I wanted to generate token manually.but generated token don't work.
[HttpPost("Activate")]
[AllowAnonymous]
public async Task<IActionResult> Activate([FromBody] SignUpPhoneModel model)
{
if (string.IsNullOrWhiteSpace(model.PhoneNumber))
{
return BadRequest("Phone Number is Empty");
}
PhoneValidation pv = new PhoneValidation();
IdentityUser CurrentUser = await db.Users.Where(e => e.PhoneNumber == model.PhoneNumber).FirstAsync();
if (!await UserManager.VerifyChangePhoneNumberTokenAsync(CurrentUser, model.ActivationCode, model.PhoneNumber))
{
return BadRequest("Activation Code is not correct");
}
else
{
//Here user is activated and should get token But How?
CurrentUser.PhoneNumberConfirmed = true;
List<string> UserRoles = (await UserManager.GetRolesAsync(CurrentUser)).ToList();
var tokenHandler = new JwtSecurityTokenHandler();
RSACryptoServiceProvider rsap = new RSACryptoServiceProvider(KeyContainerNameForSigning);
SecurityKey sk = new RsaSecurityKey(rsap.Engine);
List<Claim> UserClaims = new List<Claim>() {
new Claim(JwtRegisteredClaimNames.Sub, CurrentUser.Id),
};
foreach (var r in UserRoles)
{
UserClaims.Add(new Claim(JwtClaimTypes.Role, r));
}
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer= "https://myidentityserverapi.com",
Audience = "IdentityServerApi,MyAPI",
Subject = new ClaimsIdentity(UserClaims),
Expires = DateTime.UtcNow.AddDays(365),
SigningCredentials = new SigningCredentials(sk, SecurityAlgorithms.RsaSha512),
};
var token = tokenHandler.CreateToken(tokenDescriptor);
TokenModel tm = new TokenModel()
{
access_token = tokenHandler.WriteToken(token)
};
return Ok(tm);
}
}
When I recieve token from above(actionvation method) in my application is like below,But It don't work for example User.Identity.IsAuthenticated is false.Does any one know How can I generate token like connect/token endpoint without username and password?
"alg": "RS256",
"typ": "JWT"
}
{
"unique_name": "13f2e130-e2e6-48c7-b3ac-40f8dde8087b",
"role": "Member",
"nbf": 1600323833,
"exp": 1718259833,
"iat": 1600323833
}
Did I choose the right method? Or I should use another way for example different flow or grant types?
I suppose that your users activate their ActivationCode through IS4 server. If that's the case, you don't need to manage/generate mannually your access_token.
You have just to follow the same procedure that the Login method inside the AccountController, consisting to:
Check user with login/password, in your case validate your ActivationCode
Once user identified, SignIn your user by SignInManager. (SignInManager.SignInAsync)
Raise UserLoginSuccessEvent event.
await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId));
Finally redirect user to your web app.
return Redirect(model.ReturnUrl);
When redirecting to your application, IdentityServer4 will send to the user its access_token.
I think all you need to do is that when the user confirms the activation code you do the same things as covered in the:
public async Task<IActionResult> Login(LoginInputModel model, string button) { }
As found in the reference AccountController.cs class.
I finally create access token like this:
[HttpPost("Activate")]
[AllowAnonymous]
public async Task<IActionResult> Activate([FromBody] SignUpPhoneModel model, [FromServices] ITokenService TS, [FromServices] IUserClaimsPrincipalFactory<JooyaIdentityUser> principalFactory, [FromServices] IdentityServerOptions options)
{
JooyaIdentityUser CurrentUser = await db.Users.Where(e => e.PhoneNumber == model.PhoneNumber).FirstOrDefaultAsync();
if (!await UserManager.VerifyChangePhoneNumberTokenAsync(CurrentUser, model.ActivationCode, model.PhoneNumber))
{
return BadRequest("Activation Code in not correct");
}
CurrentUser.PhoneNumberConfirmed = true;
await db.SaveChangesAsync();
await UserManager.UpdateSecurityStampAsync(CurrentUser);
var Request = new TokenCreationRequest();
var IdentityPricipal = await principalFactory.CreateAsync(CurrentUser);
var IdentityUser = new IdentityServerUser(CurrentUser.Id.ToString());
IdentityUser.AdditionalClaims = IdentityPricipal.Claims.ToArray();
IdentityUser.DisplayName = CurrentUser.UserName;
IdentityUser.AuthenticationTime = System.DateTime.UtcNow;
IdentityUser.IdentityProvider = IdentityServerConstants.LocalIdentityProvider;
Request.Subject = IdentityUser.CreatePrincipal();
Request.IncludeAllIdentityClaims = true;
Request.ValidatedRequest = new ValidatedRequest();
Request.ValidatedRequest.Subject = Request.Subject;
Request.ValidatedRequest.SetClient(SeedConfig.GetClients().Where(e => e.ClientId == model.ClientId).First());
List<ApiResource> Apis = new List<ApiResource>();
Apis.Add(SeedConfig.GetApis().Where(e => e.Name == "IdentityServerApi").First());
Apis.Add(SeedConfig.GetApis().Where(e => e.Name == model.ApiName).First());
Request.Resources = new Resources(SeedConfig.GetIdentityResources(), Apis);
Request.ValidatedRequest.Options = options;
Request.ValidatedRequest.ClientClaims = IdentityUser.AdditionalClaims;
var Token = await TS.CreateAccessTokenAsync(Request);
Token.Issuer = HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value;
Token.Lifetime = 32000000;
var TokenValue = await TS.CreateSecurityTokenAsync(Token);
TokenModel tm = new TokenModel()
{
access_token = TokenValue
};
return Ok(tm);
}
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)