Map IdentityServer 4 claims to .NET MVC 4 client - c#

during this week i'm trying to get my client connected to my IdentityServer 4.
On the IdentityServer i've implemented the IProfileService to set some static claims on a user.
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
var principal = await _userClaimsPrincipalFactory.CreateAsync(user);
var claims = principal.Claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
context.IssuedClaims = claims;
var claims2 = new List<Claim>
{
new Claim("role", "JustSpend.Employee"),
new Claim("role", "JustSpend.Administrator"),
new Claim("fullName", string.Format("{0} {1}", user.FirstName, user.LastName)),
new Claim("canAccess", "JustSpend"),
new Claim("canAccess", "APRA"),
new Claim("canAccess", "SFVO")
};
context.IssuedClaims.AddRange(claims2);
}
At the MVC 4 client side I have the following configuration:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "NIC Identity",
Authority = "http://localhost:5000", //ID Server
ClientId = "JSpend",
ResponseType = "id_token code",
SignInAsAuthenticationType = "Cookies",
RedirectUri = "http://localhost:57895/signin-oidc", //URL of website
PostLogoutRedirectUri = "http://localhost:57895/",
Scope = "openid profile offline_access",
TokenValidationParameters = new TokenValidationParameters() { NameClaimType = "name", RoleClaimType = "role" },
ClientSecret = "secret",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
var discoveryClient = new DiscoveryClient("http://localhost:5000");
var doc = await discoveryClient.GetAsync();
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
doc.TokenEndpoint,
"JSpend",
"secret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoClient = new UserInfoClient(doc.UserInfoEndpoint);
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
// create new identity
var id = n.AuthenticationTicket.Identity;
//id.AddClaims(userInfoResponse.Claims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
},
RedirectToIdentityProvider = n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
}
});
The claims were retreived all fine in the userInfoResponse. After the user have signed in, he is redirected to the AccountController (ExternalLoginCallback). But the claims are not mapped on the User in the controller at all.
ExternalLoginCallback method:
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var result = await _signInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
Who can help me out? I'm looking for a solution the whole week, and if I Google, all the searchresults are turned purple (as visited)...

Related

What is the Problems of my Code in Claim base Authorization ASP.NET CORE WEB API?

I want to use claim base Authorization in ASP.NET CORE Web API, I think my code is true but, it does not work for me and gives me the error 403 forbidden however I use the Right Token which has the right claim value in my AspNetUserClaims table
here is my Program.cs code
//Get Connection String
builder.Services.AddDbContext<AppDbContext>(opts =>
opts.UseNpgsql(builder.Configuration["connection:connectionString"]));
builder.Services.Configure<ApplicationSettings>(builder.Configuration.GetSection("ApplicationSettings"));
builder.Services.AddMvc();
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<AppDbContext>();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("VIEW",
policy => policy.RequireClaim("VIEW"));
});
builder.Services.Configure<IdentityOptions>(options =>
options.User.RequireUniqueEmail = true);
//JWT Token Setup
var key = Encoding.UTF8.GetBytes(builder.Configuration["ApplicationSettings:JWT_Secret"]);
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x => {
x.RequireHttpsMetadata = false;
x.SaveToken = false;
x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
};
});
here is my UsersLoginController when the user login, this controller generates the Token after successful login
[HttpPost]
[Route("Login")]
public async Task<IActionResult> Login(LoginModel model)
{
var user = await _userManager.FindByNameAsync(model.UserName);
if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
{
//Get role assigned to the user
var role = await _userManager.GetRolesAsync(user);
IdentityOptions _options = new IdentityOptions();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("UserID",user.Id.ToString()),
new Claim(_options.ClaimsIdentity.RoleClaimType,role.FirstOrDefault())
}),
Expires = DateTime.UtcNow.AddDays(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.JWT_Secret)), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return Ok(new { token });
}
else
return BadRequest(new { message = "Username or password is incorrect." });
}
}
here is my UsersProfileDetailsController
[HttpGet]
[Authorize(Policy = "VIEW")]
public async Task<Object> GetUserProfile()
{
string userId = User.Claims.First(c => c.Type == "UserID").Value;
var user = await _userManager.FindByIdAsync(userId);
var role = await _userManager.GetRolesAsync(user);
return new
{
user.Id,
user.FirstName,
user.LastName,
user.Email,
user.UserName,
user.PhotoPath,
role,
};
}
Now when I login with Admin User which has the right Claimvalue VIEW in the Table AspNetUserClaims, it generates the Token for me but when I use this Token to access UserDetails from UsersProfileDetailsController it gives me the code 403 forbidden.
Can anyone help me with how to implement Claims base Authorization and what are my problems?
I use Postman for testing.
because you configured authoriztion as below:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("VIEW",
policy => policy.RequireClaim("VIEW"));
});
If you Press F12 and go to definition,you'll find the explaination of
the method
you added [Authorize(Policy = "VIEW")] on your controller, if you want authorize successfully, when you generate the token,you have to add the claim VIEW
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("UserID",user.Id.ToString()),
//add a new claim named View here
new Claim("View",.......),
new Claim(_options.ClaimsIdentity.RoleClaimType,role.FirstOrDefault())
}),

Blazor Server app + external login Facebook: user name already taken

I've implemented Facebook external login followin https://hackernoon.com/how-to-implement-facebook-authentication-and-authorization-in-server-side-blazor-app-86p3zxj
I have scaffolded all Identity pages. Because every user should be added to a specific Role I have modified Register.cshtml.cs so that I can add all new users to a role:
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
// Add all new users to the User role
await _userManager.AddToRoleAsync(user, CustomRoles.User);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
Because users who register and log in via external providers (e.g. facebook) should also be added to a specific role, I also added this part in the scaffolded ExternalLogin.cshtml.cs page:
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
// Add all new users to the User role
await _userManager.AddToRoleAsync(user, CustomRoles.User);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
When going to https://localhost:44390/Identity/Account/Login it is possible to click the Facebook button under the "Use another service to log in":
This takes me to page: https://localhost:44390/Identity/Account/ExternalLogin?returnUrl=%2F&handler=Callback#=
now when I click "Register" button I get:
This fails in the ExternalLogin.cshtml.csĂ fter thevar result = await _userManager.CreateAsync(user)`:
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user);
if (result.Succeeded) // <-- no success
My startup configureservices:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>(options => {
// Require email confirmation (prevents registered users from logging in until their email is confirmed)
options.SignIn.RequireConfirmedEmail = true;
// Password Settings
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+";
options.User.RequireUniqueEmail = true; // True if the application requires each user to have their own, unique email, otherwise false.
})
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddDefaultUI();
services.AddRazorPages();
services.AddServerSideBlazor().AddCircuitOptions(options =>
{
if (Env.IsDevelopment()) //only add details when debugging
{
options.DetailedErrors = true;
}
});
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<ApplicationUser>>();
//services.AddSingleton<WeatherForecastService>();
services.AddAuthentication().AddFacebook(facebookOptions =>
{
facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
facebookOptions.Events = new OAuthEvents()
{
OnRemoteFailure = loginFailureHandler =>
{
var authProperties = facebookOptions.StateDataFormat.Unprotect(loginFailureHandler.Request.Query["state"]);
loginFailureHandler.Response.Redirect("/Identity/Account/Login");
loginFailureHandler.HandleResponse();
return Task.FromResult(0);
}
};
});
services.AddAuthorization(config =>
{
config.AddPolicy(Policies.IsAdmin, Policies.IsAdminPolicy());
config.AddPolicy(Policies.IsSuperUser, Policies.IsSuperUserPolicy());
config.AddPolicy(Policies.IsUser, Policies.IsUserPolicy());
});
var emailCfg = Configuration.GetSection("EmailSenderOptions");
services.Configure<EmailSenderOptions>(emailCfg);
services.AddTransient<IEmailSender, EmailSender>();
services.AddTransient<IReservationsService, ReservationsService>();
services.AddTransient<IGeoService, GeoService>();
}
I can understand that CreateAsync in ExternalLogin will fail when a user already exists, but how should this be modified? As the first time if the user doesn't exist and logs in via facebook, it should be created. When the user logs in again it should just log in.
Is this default scaffolded code incorrect or am I missing any configurations or modifying the externallogin in the wrong place?

Token and JWT with dotnet Core 3.1 User Identification, localStorage.getItem("token") is null

I can not get 'token' value from Angular App. it returns null.
Please any one pinpoint what I have done wrong.
Here is the code in startup.cs
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
//Jwt Authentication
var key = System.Text.Encoding.UTF8.GetBytes(Configuration["ApplicationSettings:JWT_Secret"].ToString());
//services.AddAuthentication()
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(token =>
{
token.RequireHttpsMetadata = false;
token.SaveToken = true;
token.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
};
})
.AddIdentityServerJwt();
//End of Jwt Authentication
//services.AddControllersWithViews();
services.AddRazorPages();
//here is the code from login.cshtml.cs
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
//
// add JWT to create Token
{
_logger.LogInformation("Yitong Create Token.");
// logic to get token from GenerateJSONWebToken
var tokenString = GenerateJSONWebToken();
// end changed
//_logger.LogInformation(tokenString);
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
private string GenerateJSONWebToken()
{
var sec = Encoding.UTF8.GetBytes("1234567890123456");
var securityKey = new SymmetricSecurityKey(sec);
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[] {
new Claim(ClaimTypes.UserData, User.Identity.Name)
};
var token = new JwtSecurityToken(
"self", // issuer
"self", // Audience
null, // replace null with claims
expires: DateTime.Now.AddMinutes(120),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
here is my code in angular
to get data from sql server
the token is = null
getForeCast() {
const token = localStorage.getItem("token");
const httpOptions = {
headers: new HttpHeaders({
'Authorization': 'Bearer ' + token
})
};
return this.http.get<forecastModel.weatherForecast_custom[]>(this.url, httpOptions)
.pipe(
tap(() => console.log("HTTP request executed")),
catchError(err => {
console.log('Handling error locally and rethrowing it...', err);
return throwError(err);
}),
finalize(() => console.log("first finalize() block executed")),
)
};
Anyone please?
I don't know why I can not post this it says
It looks like your post is mostly code; please add some more details.'
This is the best I can describe the issue I have had.
Thank you

ASP.Net Core JWT Login not setting HttpContext.User.Claims

I have the following for my login code, and another method to retrieve the user ID in another call.
// POST: /api/Account/Login
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody]LoginViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(model.Email);
if (user != null)
{
if (!await _userManager.IsEmailConfirmedAsync(user))
{
ModelState.AddModelError(string.Empty,
"You must have a confirmed email to log in.");
return BadRequest(Errors.AddErrorToModelState("Email", "You must have a confirmed email to log in.", ModelState));
}
}
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
// IS THIS NEEDED? --> // HttpContext.User = await _userClaimsPrincipalFactory.CreateAsync(user);
var tokens = _antiforgery.GetAndStoreTokens(HttpContext);
var identity = _jwtFactory.GenerateClaimsIdentity(model.Email, user.Id);
_logger.LogInformation(1, "User logged in.");
var jwt = await Tokens.GenerateJwt(identity, _jwtFactory, model.Email, _jwtOptions, new JsonSerializerSettings { Formatting = Formatting.Indented });
return new OkObjectResult(jwt);
}
if (result.RequiresTwoFactor)
{
//return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
}
if (result.IsLockedOut)
{
string message = "User account locked out.";
_logger.LogWarning(2, message);
return BadRequest(Errors.AddErrorToModelState("Email", message, ModelState));
}
if (result.IsNotAllowed)
{
string message = "User account is not allowed to sign in.";
_logger.LogWarning(2, message);
return BadRequest(Errors.AddErrorToModelState("Email", message, ModelState));
}
return BadRequest(Errors.AddErrorToModelState("", "Sign in failed.", ModelState));
}
return BadRequest(Errors.AddErrorToModelState("", "", ModelState));
}
public string GetUserId()
{
string username = string.Empty;
ClaimsPrincipal principal = HttpContext.User as ClaimsPrincipal;
if (HttpContext.User != null)
{
var id = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier);
if (id != null)
{
username = id.Value;
}
}
return username;
}
This is the code for GenerateJwt. (Are these fields standard? I see other fields being set in other code, such as in https://code-maze.com/authentication-aspnetcore-jwt-1/.)
public static async Task<string> GenerateJwt(ClaimsIdentity identity, IJwtFactory jwtFactory,string userName, JwtIssuerOptions jwtOptions, JsonSerializerSettings serializerSettings)
{
var response = new
{
id = identity.Claims.Single(c => c.Type == "id").Value,
auth_token = await jwtFactory.GenerateEncodedToken(userName, identity),
expires_in = (int)jwtOptions.ValidFor.TotalSeconds
}; // see GenerateEncodedToken pasted below.
return JsonConvert.SerializeObject(response, serializerSettings);
}
public async Task<string> GenerateEncodedToken(string userName, ClaimsIdentity identity)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userName),
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
identity.FindFirst(Helpers.Constants.Strings.JwtClaimIdentifiers.Rol),
identity.FindFirst(Helpers.Constants.Strings.JwtClaimIdentifiers.Id)
};
// Create the JWT security token and encode it.
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);
return encodedJwt;
}
These are the relevant lines in ConfigureServices.
// jwt wire up
// Get options from app settings
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
// Configure JwtIssuerOptions
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(configureOptions =>
{
configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
configureOptions.RequireHttpsMetadata = false;
});
// api user claim policy
services.AddAuthorization(options =>
{
options.AddPolicy("ApiUser", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
});
// add identity
var builder = services.AddIdentity<AppUser, AppRole>(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
})
.AddSignInManager<SignInManager<AppUser>>()
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
I'm using the following to pass my token from Angular.
private authHeader(): HttpHeaders {
let headers = new HttpHeaders();
if (isPlatformBrowser(this.platformId)) {
headers = headers.set('Content-Type', 'application/json');
let authToken = this.windowRefService.nativeWindow.localStorage.getItem('auth_token');
headers = headers.set('Authorization', authToken);
headers = headers.set('X-XSRF-TOKEN', this.getCookie('XSRF-TOKEN'));
}
return headers;
}
protected jsonAuthRequestOptions = { headers: this.authHeader() };
public loggedInAs(): Observable<string> {
return this.http.post<any>(this.baseUrl + "api/Account/GetUserId", { } , this.jsonAuthRequestOptions)
.map(data => {
if (data) {
return data;
}
return '';
})
.catch(this.handleError);
}
Does JWT authentication automatically set HttpContext.User? Or do I have to do that explicitly?
My succeeding call fails. (User has no claims, though HttpContext.User is not null.) I don't know whether the problem is with relying on HttpContext.User, or whether there is something wrong with the token generation / consumption.
Had a similar problem, Claims from token did not pass to HttpContext.User.Identity.Claims.
In my case, changing the order of adding services helped.
In startup.cs I've got now:
services.AddIdentity();
services.AddAuthentication();

SignInManager.ExternalSignInAsync returns SignInStatus.Failure after registering, always

In my mixed MVC and WebAPI app, the ExternalLoginCallback to authenticate with Google and Facebook contains
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
which is always SignInStatus.Failure, even after registering with Google and Facebook
Here is my ExternalLoginCallback (it's default)
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
if (returnUrl == null)
{
using (var context = ApplicationDbContext.Create())
{
var user = context.Users.FirstOrDefault(x => x.Email.Equals(loginInfo.Email));
return RedirectToAction("Dashboard", "Provider");
}
}
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
And here's my ExternalLoginConfirmation
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Dashboard", "Provider");
}
if (ModelState.IsValid)
{
// Get the information about the user from the external login provider
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null)
{
return View("ExternalLoginFailure");
}
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
user.IsProvider = true;
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
result = await UserManager.AddLoginAsync(user.Id, info.Login);
var provider = new Provider
{
UserId = user.Id,
Id = Guid.NewGuid(),
CompanyName = model.CompanyName,
TierType = model.TierType
};
using (var context = ApplicationDbContext.Create())
{
context.Providers.Add(provider);
await context.SaveChangesAsync();
}
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
if (returnUrl != null)
return RedirectToLocal(returnUrl);
else
return RedirectToAction("Dashboard", "Provider");
}
}
AddErrors(result);
}
ViewBag.ReturnUrl = returnUrl;
return View(model);
}
This is my StartupConfigureAuth
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
///////////////////////////////////////
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AccessTokenFormat = new TicketDataFormat(app.CreateDataProtector(
typeof(OAuthAuthorizationServerMiddleware).Namespace,
"Access_Token", "v1")),
AccessTokenProvider = new AuthenticationTokenProvider(),
AllowInsecureHttp = true
//TODO ^ change
};
// Enable the application to use bearer tokens to authenticate users
//app.UseOAuthBearerTokens(OAuthOptions);
//app.UseOAuthAuthorizationServer(OAuthOptions);
//TODO ^ with v
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
//OAuthBearerOptions.AccessTokenFormat = OAuthOptions.AccessTokenFormat;
//OAuthBearerOptions.AccessTokenProvider = OAuthOptions.AccessTokenProvider;
//OAuthBearerOptions.AuthenticationMode = OAuthOptions.AuthenticationMode;
//OAuthBearerOptions.AuthenticationType = OAuthOptions.AuthenticationType;
//OAuthBearerOptions.Description = OAuthOptions.Description;
//OAuthBearerOptions.Provider = new ApplicationOAuthProvider();
//OAuthBearerOptions.SystemClock = OAuthOptions.SystemClock;
//This will used the HTTP header: "Authorization" Value: "Bearer 1234123412341234asdfasdfasdfasdf"
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
///////////////////////////////////////////
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
//// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
//// Enables the application to remember the second login verification factor such as phone or email.
//// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
//// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
//// Uncomment the following lines to enable logging in with third party login providers
////app.UseMicrosoftAccountAuthentication(
//// clientId: "",
//// clientSecret: "");
////app.UseTwitterAuthentication(
//// consumerKey: "",
//// consumerSecret: "");
app.UseFacebookAuthentication(
appId: "XXXX",
appSecret: "XXXX");
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = "XXXX",
ClientSecret = "XXXX"
});
}

Categories

Resources