It is my first time to use ASP.NET core 2.2 Identity to manage Authorization in Controller. In the client side, I use (Login.cshtml.cs) SignInManger.PasswordSignInAsync(…) to sign in a user. After user sign in successfully, I send HttpClient call to UsersController in another project to get more user’s info. However, the Controller is does not receive the the Claims.ClaimsPrincipal (User) information from the Request. The [Authorize(Roles = “admin”)] fails but [AllowAnonymous] passes. I checked the Request cookie. The User HttpContext is not updated to the signed in user in client side. To be exact, I don’t know how to update the User in cookie. I thought it is done in the background. I have looked through many posts but none of them explicitly mentions how the User is being updated in the cookie and send to the controller. It seems that the User updated is done after the program exits the Login.cshtml.cs. I am able to get the Identity of the user by using SingInManager.CreateUserPrincipalAsync(…) within public async Task OnPostAsync(string returnUrl = null) of Login.cshtml.cs. But I don’t know how to pass that Identity to the User in cookie and send it to Controller.
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
// test: get the Identity of the Input.Email
var user = await _signInManager.UserManager.FindByEmailAsync(Input.Email);
var userPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
var identity = userPrincipal.Identity;
// * User Identity does not populate at this point.
var ret = await GetRestOfUserInfo();
}
}
return Page();
}
private async Task<int> GetRestOfUserInfo()
{
var userStatus = await _userManager.GetUserAsync(User);
UserCSDto userCDDto = new UserCSDto()
{
Email = Input.Email,
UserName = Input.Email,
Password = Input.Password,
RememberMe = Input.RememberMe
};
CookieContainer cookieContainer = new CookieContainer();
HttpClientHandler handler = new HttpClientHandler
{
UseCookies = true,
UseDefaultCredentials = true,
CookieContainer = cookieContainer
};
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(JsonConvert.SerializeObject(userCDDto), Encoding.UTF8, "application/json");
var url = _connectionSettings.UrlConnection + "/api/users/authenticate";
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode)
{
var respUser = JsonConvert.DeserializeObject<User>(response.Content.ReadAsStringAsync().Result);
var _responseToken = response.Headers.Where(t => t.Key == "Authorization")
.Select(t => t.Value);
var token = (_responseToken.FirstOrDefault()).FirstOrDefault();
applicationUser.PreferredName = respUser.PreferredName;
applicationUser.Token = token;
ApplicationUser user = await _userManager.FindByEmailAsync(Input.Email);
user.Token = token;
user.PreferredName = respUser.PreferredName ?? "";
IEnumerable<Claim> claims = new Claim[]
{
new Claim("Token", token),
new Claim("PreferredName", "123abc") // respUser.PreferredName)
};
await _userManager.AddClaimsAsync(user, claims);
return 1;
}
return 0;
}
[Authorize]
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class UsersController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private IUserService _userService;
public UsersController(UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
:
:
[Authorize(Roles = "any role")] // failed because User does not contain any info
[AllowAnonymous] // works because no authorization check
[HttpPost("authenticate")]
public async Task<IActionResult> Authenticate([FromBody]UserCSDto userParam)
{
var username = User.Identity.Name; // username is null because User does not
// have info.
var user = _userService.Authenticate(userParam.UserName, userParam.Password);
if (user == null)
return BadRequest(new { message = "Username or passwrod is incorrect" });
var token = _jwtTokenService.CreateToken(userCSDto);
Response.Headers.Add("Authorization", "Bearer " + token);
Response.Headers.Add("Content-Type", "application/json");
return Ok(userCSDto);
}
:
:
}
/////////////////////////////
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
services.Configure<ConnectionSettings>(Configuration.GetSection("ConnectionStrings"));
var connection = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<EFCoreContext>(
options => options.UseSqlServer(connection,
b => b.MigrationsAssembly("EFStructures"))); // from Data project
services.AddHttpContextAccessor(); // #.Net Core 2.2
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.Expiration = TimeSpan.FromHours(1);
options.SlidingExpiration = true;
});
services.AddSession(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.Name = "Test.Session";
options.IdleTimeout = TimeSpan.FromMinutes(60);
options.Cookie.Path = "/";
});
services.Configure<IdentityOptions>(options =>
{
// 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;
// Require Confirmed and Unique Email
options.User.RequireUniqueEmail = true;
options.SignIn.RequireConfirmedEmail = false;
});
#region authenication using JWT token
services.Configure<JwtTokenSettings>(Configuration.GetSection("JwtTokenSettings"));
///// configure jwt authentication
var jwtTokenSettings = Configuration.GetSection("JwtTokenSettings").Get<JwtTokenSettings>();
var key = Encoding.ASCII.GetBytes(jwtTokenSettings.Secret);
//Adds cookie middleware to the services collection and configures it
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => options.LoginPath = new PathString("/account/login"));
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 = false,
ValidateAudience = false,
//ValidateIssuer = Configuration["AppSettings:Issuer"],
//ValidateAudience = Configuration["AppSettings:Issuer"],
ValidateLifetime = true
};
});
#endregion
#region Add Role services to Identity RAZOR PAGE only
services.AddIdentity<ApplicationUser, IdentityRole>()
// .AddDefaultUI(Microsoft.AspNetCore.Identity.UI.UIFramework.Bootstrap4)
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<EFCoreContext>();
#endregion
#region Setting for using Identity
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
#endregion Setting for using Identity
services.AddHttpClient();
services.AddScoped<IJwtTokenService, JwtTokenService>();
services.AddScoped<IEmailSenderService, EmailSenderService>();
services.AddScoped<IEmailSender, EmailSender>();
#region Get EmailSettings from appsettings.json
services.AddOptions();
services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
#endregion Get EmailSettings from appsettings.json
services.AddMvc()
.ConfigureApiBehaviorOptions(options =>
{
options
.SuppressUseValidationProblemDetailsForInvalidModelStateResponses = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseAuthentication();
app.UseSession();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRequestLocalization();
app.UseMvcWithDefaultRoute();
// global cors policy
app.UseCors(x => x
.WithOrigins("https://localhost")
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseMvc();
}
}
Related
I have been trying to getting this fixed for many times already, but have no success so far. I am logging into my page and everything works fine. However every time I am reloading my page I am ending up signed out and redirected to "Login page" and have to sign in again. What is the problem? Is there is something messed up with my Coockie logic?
I have also tried to implement login.RememberMe logic, but it does not work either. I have checked that login.RememberMe returns true, but no effect.
Login method in controller:
[HttpPost]
public async Task<IActionResult> Login([FromBody] LoginModel login)
{
ApplicationUser user = await this.SignInManager.UserManager.FindByEmailAsync(login.Email);
Microsoft.AspNetCore.Identity.SignInResult result =
await this.SignInManager.PasswordSignInAsync(login.Email, login.Password, login.RememberMe, false);
if (!result.Succeeded)
{
List<string> errors = new List<string>();
errors.Add("Email and password are invalid.");
return BadRequest(new LoginResult
{
Successful = false,
Errors = errors,
});
}
IList<string> roles = await this.SignInManager.UserManager.GetRolesAsync(user);
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, login.Email)
};
ClaimsIdentity identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
AuthenticationProperties props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMonths(1)
};
// to register the cookie to the browser
this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, props).Wait();
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.Configuration["JwtSecurityKey"]));
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
DateTime expiry = DateTime.Now.AddDays(Convert.ToInt32(this.Configuration["JwtExpiryInDays"]));
JwtSecurityToken token = new JwtSecurityToken(
this.Configuration["JwtIssuer"],
this.Configuration["JwtAudience"],
claims,
expires: expiry,
signingCredentials: creds
);
return Ok(new LoginResult
{
Successful = true,
Token = new JwtSecurityTokenHandler().WriteToken(token),
});
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSecurityKey"]))
};
})
.AddCookie(options =>
{
options.Cookie.Name = "DashCoockie";
options.LoginPath = "/login";
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true;
options.EventsType = typeof(CookieAuthEvent);
});
services.AddScoped<CookieAuthEvent>();
services.AddAuthorization(config =>
{
config.AddPolicy(Policies.IsAdmin, Policies.IsAdminPolicy());
config.AddPolicy(Policies.IsUser, Policies.IsUserPolicy());
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddControllers();
// Instant update on runtime for development purposes
services.AddControllersWithViews().AddRazorRuntimeCompilation();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy();
}
CookieAuthEvent.cs:
public class CookieAuthEvent : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
context.Request.HttpContext.Items.Add("ExpiresUTC", context.Properties.ExpiresUtc);
}
}
After logging in (Coockie name is the same, just hided first part as it is in the code above):
My project structure is as follows: Client, Server, Shared. I found solution by using LocalStorage and AuthService : IAuthService on Client side. So is it so that in my setup the only solution is to use LocalStorage?
public class AuthService : IAuthService
{
private readonly HttpClient _httpClient;
private readonly AuthenticationStateProvider _authenticationStateProvider;
private readonly ILocalStorageService _localStorage;
public AuthService(HttpClient httpClient,
AuthenticationStateProvider authenticationStateProvider,
ILocalStorageService localStorage)
{
_httpClient = httpClient;
_authenticationStateProvider = authenticationStateProvider;
_localStorage = localStorage;
}
public async Task<RegisterResult> Register(RegisterModel registerModel)
{
var result = await _httpClient.PostJsonAsync<RegisterResult>("api/accounts", registerModel);
return result;
}
public async Task<LoginResult> Login(LoginModel loginModel)
{
var loginAsJson = JsonSerializer.Serialize(loginModel);
var response = await _httpClient.PostAsync("api/Login", new StringContent(loginAsJson, Encoding.UTF8, "application/json"));
var loginResult = JsonSerializer.Deserialize<LoginResult>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (!response.IsSuccessStatusCode)
{
return loginResult;
}
await _localStorage.SetItemAsync("authToken", loginResult.Token);
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(loginModel.Email);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginResult.Token);
return loginResult;
}
public async Task Logout()
{
await _localStorage.RemoveItemAsync("authToken");
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsLoggedOut();
_httpClient.DefaultRequestHeaders.Authorization = null;
}
}
It seems that you didn't set the token in your request header
try add these codes in your startup class:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["access_token"];
return Task.CompletedTask;
}
};
});
In your Login Action :
Response.Cookies.Append("access_token",your JwtSecurityToken)
I am new in asp.net core. I try to login with discord as 3rd party login service (like sign-in with facebook, google).
I can login successfully and have my user object, claims and I can enter a class which has an authorize attribute. Below you can see that UserIdentity is fine.
But let assume that user wants to go back to the login page. In this case, I have to redirect him to the index but I want to check whether the user is authenticated or not by using Identity and unfortunately, it is false and no claims etc. As I understand, it may be related with cookies or something similar. I also use different attribute for class (not authorize but AllowAnonymous) You can see below my Identity object
I am sharing my authentication code
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.Cookie.MaxAge = options.ExpireTimeSpan;
options.SlidingExpiration = true;
options.EventsType = typeof(CustomCookieAuthenticationEvents);
options.AccessDeniedPath = "/auth/DiscordAuthFailed";
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration.GetValue<string>("Jwt:Issuer"),
ValidAudience = Configuration.GetValue<string>("Jwt:Audience"),
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration.GetValue<string>("Jwt:EncryptionKey")))
};
})
.AddOAuth("Discord",
options =>
{
options.AuthorizationEndpoint = "https://discord.com/api/oauth2/authorize";
options.TokenEndpoint = "https://discord.com/api/oauth2/token";
options.Scope.Add("identify");
options.Scope.Add("email");
options.Scope.Add("guilds.join");
options.Scope.Add("guilds.members.read");
options.CallbackPath = "/auth/oauthCallback";
options.ClientId = Configuration.GetValue<string>("Discord:ClientId");
options.ClientSecret = Configuration.GetValue<string>("Discord:ClientSecret");
options.UserInformationEndpoint = "https://discord.com/api/users/#me";
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "username");
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
options.ClaimActions.MapJsonKey(ClaimTypes.IsPersistent, "verified");
options.AccessDeniedPath = "/auth/DiscordAuthFailed";
options.Events = new OAuthEvents()
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get,
context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user=(await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync())).RootElement;
context.RunClaimActions(user);
}
};
});
services.AddTransient();
So my question is that, what is the best approach to access userIdentify object in any class/method after successfully login?
After very long analyses, I found the problem.
I just changed the DefaultAuthenticateScheme as CookieAuthenticationDefaults
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
and called sign method in my login method.
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, result.Principal);
after successfully sign-in, I cna get HttpContext.User object in any action.
You can use the GetUserAsync method that will check if a user has been logged in. You need to use the UserManager class that falls under the AspNetCore.Identity to a use the above method. In your case, it will look something like this:
You will first need to configure your UserManager class in Startup.cs by simply adding a parameter to the Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
{
// your code
}
And then you can use it in your Controller method:
[Route("Account")]
public class AccountController: Controller
{
private UserManager<ApplicationUser> _userManager;
public AccountController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[Route("Login")]
[AllowAnonymous]
public IActionResult Login()
{
ClaimsPrincipal currentUser = User;
var user =_userManager.GetUserAsync(User).Result;
if(user.Identity.IsAuthenticated)
{
//redirect here
}
return View();
}
}
You need to update your ConfigureServices to include the Default Identity:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
}
I have implemented Identity Server for Auth Code Flow.
What is correct way to persist the claims (in OnTicketReceived or OnTicketValidated as shown below), so that in subsequent calls
to Blazor pages, I could receive User aka ClaimPrincipal populated for
my use?
Here is code of middleware of my resource server:
public void ConfigureServices(IServiceCollection services)
{
//....
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
options.SignIn.RequireConfirmedEmail = false;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<SomeContext>()
.AddDefaultTokenProviders();
Middleware integration:
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
options.DefaultSignOutScheme = "oidc";
})
.AddCookie("cookie", options =>
{
options.Cookie.Name = "__Host-bff";
options.Cookie.SameSite = SameSiteMode.Strict;
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc.code";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.ResponseMode = "query";
options.GetClaimsFromUserInfoEndpoint = true;
options.MapInboundClaims = false;
options.SaveTokens = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
//Critical Parts
options.TokenValidationParameters = new()
{
NameClaimType = "name",
RoleClaimType = "role"
};
Authenticated callback from identity server is as following:
options.Events.OnTicketReceived = async n =>
{
var serviceProvider = n.HttpContext.RequestServices;
var accountService = serviceProvider.GetService<IAccountService>() ?? throw new ArgumentNullException("serviceProvider.GetService<IAccountService>()");
I tried using BlazoredSessionStorage etc. but it seems too early to
invoke that. We have to wait until OnPrerender or OnInit
I also tried CustomTokenStore. But how does the claim from cookie
come back to server?
var svc = n.HttpContext.RequestServices.GetRequiredService<IUserAccessTokenStore>();
if (n.Principal != null)
{
var userName = n.Principal.FindFirst(x => x.Type == "name")?.Value;
await accountService.UserCreateAsync(new NewAccount
{
Username = userName,
FirstName = userFirstName,
LastName = userLastName,
//ContactId = 100,
TenantId = 1
});
await (authProvider as SomeAuthenticationStateProvider).LoginAsync(new AuthenticationLogin { Username = userName }, 24 * 60);
}
};
public class CustomTokenStore : IUserAccessTokenStore
{
ConcurrentDictionary<string, UserAccessToken> _tokens = new ConcurrentDictionary<string, UserAccessToken>();
public Task ClearTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
_tokens.TryRemove(sub, out _);
return Task.CompletedTask;
}
public Task<UserAccessToken> GetTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
_tokens.TryGetValue(sub, out var value);
return Task.FromResult(value);
}
public Task StoreTokenAsync(ClaimsPrincipal user, string accessToken, DateTimeOffset expiration, string refreshToken = null, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
var token = new UserAccessToken
{
AccessToken = accessToken,
Expiration = expiration,
RefreshToken = refreshToken
};
_tokens[sub] = token;
return Task.CompletedTask;
}
}
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.ClaimActions.Add(new CustomClaimsFactory(
"userName",
"XXXXX#outlook.com"
));
options.TokenValidationParameters = new TokenValidationParameters
{
// Instead of using the default validation (validating against a single issuer value, as we do in
// line of business apps), we inject our own multitenant validation logic
ValidateIssuer = false,
// If the app is meant to be accessed by entire organizations, add your issuer validation logic here.
//IssuerValidator = (issuer, securityToken, validationParameters) => {
// if (myIssuerValidationLogic(issuer)) return issuer;
//}
};
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = context =>
{
// If your authentication logic is based on users then add your logic here
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/Error");
context.HandleResponse(); // Suppress the exception
return Task.CompletedTask;
},
// If your application needs to do authenticate single users, add your user validation below.
//OnTokenValidated = context =>
//{
// var claims = new List<Claim>
// {
// new Claim(ClaimTypes.Role, "superadmin")
// };
// var appIdentity = new ClaimsIdentity(claims);
// context.Principal.AddIdentity(appIdentity);
// return Task.CompletedTask;
// //return myUserValidationLogic(context.Ticket.Principal);
//}
};
});
While CustomActionFactory class is defined below
public class CustomClaimsFactory : ClaimAction
{
string _ClaimType;
string _ValueType;
public CustomClaimsFactory(string claimType, string valueType) : base(claimType, valueType)
{
_ClaimType = claimType;
_ValueType = valueType;
}
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
{
identity.AddClaim(new Claim(_ClaimType, _ValueType, issuer));
}
}```
OnTokenValidated has a method to add the new identity you create with the claim:
OnTokenValidated = ctx =>{ ctx.Principal.AddIdentity(myNewClaim); }
I'm trying to create some roles for my web application but it isn't really working because of a Tkey exception.
I'm happy if you give an upvote so the other guys who need help can see it probably more.
I don't know how I can fix it. I think there is a problem with my Startup.cs.
Whatever i try to add the DefaultIdentity and adding the roles.
Startup.cs - On this line I get an Error:
services.AddDefaultIdentity<IdentityRole>().AddRoles<IdentityRole>().AddDefaultUI().AddEntityFrameworkStores<VerwaltungsprogrammContext>();
This is the Error Message:
>AddEntityFrameworkStores can only be called with a user that derives from IdentityUser
namespace Verwaltungsprogramm
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSession();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<VerwaltungsprogrammContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("VerwaltungsprogrammContext")));
//services.AddDefaultIdentity<IdentityUser>();
--------------> services.AddDefaultIdentity<IdentityRole>().AddRoles<IdentityRole>().AddDefaultUI().AddEntityFrameworkStores<VerwaltungsprogrammContext>(); <--------------
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
options.Conventions.AuthorizeAreaFolder("Logins", "/Create");
options.Conventions.AuthorizeAreaPage("Logins", "/Logout");
});
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Logins/Index";
options.LogoutPath = $"/Logins/Logout";
options.AccessDeniedPath = $"/Cars/Index";
});
//Password Strength Setting
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+";
options.User.RequireUniqueEmail = false;
});
//Seting the Account Login page
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Logins/Create"; // If the LoginPath is not set here, ASP.NET Core
will default to /Account/Login
options.AccessDeniedPath = "/Cars/Index"; // If the AccessDeniedPath is not set here,
ASP.NET Core will default to /Account/AccessDenied
options.SlidingExpiration = true;
});
services.AddSingleton<IEmailSender, EmailSender>();
}
public class EmailSender : IEmailSender
{
public Task SendEmailAsync(string email, string subject, string message)
{
return Task.CompletedTask;
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseSession();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Seed.CreateRoles(serviceProvider, Configuration).Wait();
}
}
}
Error:
AddEntityFrameworkStores can only be called with a user that derives from IdentityUser
The Seed.cs file is to create some roles
Here is my Seed.cs
namespace Verwaltungsprogramm
{
public static class Seed
{
public static async Task CreateRoles(IServiceProvider serviceProvider, IConfiguration Configuration)
{
//adding customs roles
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
string[] roleNames = { "Admin", "Manager", "Member" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
// creating the roles and seeding them to the database
var roleExist = await RoleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
// creating a super user who could maintain the web app
var poweruser = new ApplicationUser
{
UserName = Configuration.GetSection("AppSettings")["UserEmail"],
Email = Configuration.GetSection("AppSettings")["UserEmail"]
};
string userPassword = Configuration.GetSection("AppSettings")["UserPassword"];
var user = await UserManager.FindByEmailAsync(Configuration.GetSection("AppSettings")["UserEmail"]);
if (user == null)
{
var createPowerUser = await UserManager.CreateAsync(poweruser, userPassword);
if (createPowerUser.Succeeded)
{
// here we assign the new user the "Admin" role
await UserManager.AddToRoleAsync(poweruser, "Admin");
}
}
}
}
}
Does the error also occur if you write the line in your Startup.cs like this?
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<VerwaltungsprogrammContext>();
I fixed it by creating the project again and switch to user Account authentication, for everyone who has the same problem, I recommend doing that.
I'm learning C#/.NET and am trying to enable cookie authorization. I've read the docs and tried to implement their methodology but just can't get it to work. Every time I successfully login with the Authorization attribute enabled, I get redirected back to my sign in page, as per my settings in Startup.cs. Thanks in advance for any help or suggestions.
My code below:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddSession();
services.AddDbContext<UserDashContext>(options => options.UseNpgsql(Configuration["DBInfo:ConnectionString"]));
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<UserDashContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.User.RequireUniqueEmail = true;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
InitializeRoles(app.ApplicationServices).Wait();
loggerFactory.AddConsole();
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "Cookies",
LoginPath = "/signin",
AccessDeniedPath = new PathString("/notAllowedRoute"),
AutomaticAuthenticate = false,
AutomaticChallenge = true
});
app.UseIdentity();
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseSession();
app.UseMvc();
}
Controller.cs - Login Method
public async Task<IActionResult> Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
User LoggingIn = _context.users.Where(u => u.Email == model.Email).SingleOrDefault();
// 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(model.Email, model.Password, false, false);
if (result.Succeeded)
{
return RedirectToAction("Index", "User");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
TempData["PWError"] = "Invalid login attempt.";
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Controller.cs with Authorization
namespace UserDashboard.Controllers
{
[Authorize(ActiveAuthenticationSchemes = AuthScheme)]
public class UserController : Controller
{
private const string AuthScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
UserDashContext _context;
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
public UserController(UserDashContext context, UserManager<User>
userManager,
SignInManager<User> signInManager)
{
_context = context;
_userManager = userManager;
_signInManager = signInManager;
}
[HttpGet]
[Route("dashboard")]
public IActionResult Index()
{
return View();
}
}
}
I ended up removing app.UseCookieAuthentication and adding cookie settings to services.Configure. Updated code below:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddSession();
services.AddDbContext<UserDashContext>(options => options.UseNpgsql(Configuration["DBInfo:ConnectionString"]));
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<UserDashContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
// Cookie settings
options.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromDays(150);
options.Cookies.ApplicationCookie.LoginPath = "/signin";
options.Cookies.ApplicationCookie.LogoutPath = "/logout";
options.User.RequireUniqueEmail = true;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
InitializeRoles(app.ApplicationServices).Wait();
loggerFactory.AddConsole();
// app.UseCookieAuthentication(new CookieAuthenticationOptions()
// {
// AuthenticationScheme = "Cookies",
// LoginPath = "/signin",
// AccessDeniedPath = new PathString("/notAllowedRoute"),
// AutomaticAuthenticate = false,
// AutomaticChallenge = true
// });
app.UseIdentity();
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseSession();
app.UseMvc();
}