I'm trying to create asp.net core mvc 6 app using Cookie Middleware authentication.
My code compiles without errors, but even after successful login i'm not authorized user
Here's my startup.cs configuration
app.UseCookieAuthentication(options =>
{
options.AuthenticationScheme = "CookieAuth";
options.LoginPath = new PathString("/Account/Login/");
options.AccessDeniedPath = new PathString("/Account/Login/");
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
});
Also login action in my controller:
public async Task<IActionResult> Login(LoginViewModel model)
{
User foundUser = _userManager.findUser(model.UserName, model.Password);
if (foundUser != null)
{
List<Claim> userClaims = new List<Claim>
{
new Claim("userId", Convert.ToString(foundUser.UserID)),
new Claim(ClaimTypes.Name, foundUser.UserName),
new Claim(ClaimTypes.Role, Convert.ToString(foundUser.RoleID))
};
ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(userClaims));
await HttpContext.Authentication.SignInAsync("CookieAuth", principal);
return RedirectToAction("Index", "Dashboard");
}
return View();
}
And finally Dashboard/Index action
[Authorize]
public IActionResult Index()
{
return View();
}
I put some breakpoints in login action and everything seems works fine.
Cookie is also set correctly.
And now I don't know way i can't go to dashboard/index after sign in.
Each time i'm redirected to /Account/Login/ due to configuration settings
What am I doing wrong ?
When you construct your ClaimsIdentity in your login, you need to use a different constructor that specifies the authenticationType.
Instead of
ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(userClaims));
You should do:
ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(userClaims, "local"));
It is now possible to create a ClaimsIdentity that has claims, but
having IsAuthenticated set to false. Actually this is the default now...
To have IsAuthenticated set to true, you need to specify an authentication type
I got this info from Dominick Baier's blog here.
There is also a great example of using the cookie middleware here, also by (the legendary) Dominick Baier / leastprivilege.
EDIT:
This answer contains more information about what should be used for the authenticationType string.
Related
I'm building a ASP.NET Core (v3.1) module and I just managed to configure an OpenIdConnect Authentication. Now, I need to get all User's roles from an API in order to give or deny access to them, then I added multiple Claim values to the same Claim role "ClaimTypes.Role" in the User's Claims list through OnAuthorizationCodeReceived Event like so:
OnAuthorizationCodeReceived = async (context) =>
{
// Uses the authentication code and gets the access and refresh token
var client = new HttpClient();
var response = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest()
{
Address = urlServer + "/connect/token",
ClientId = "hybrid",
Code = context.TokenEndpointRequest.Code,
RedirectUri = context.TokenEndpointRequest.RedirectUri,
}
if (response.IsError) throw new Exception(response.Error);
var identity = new ClaimsIdentity(context.Principal.Identity);
var listRoles = GenericProxies.RestGet<List<string>>(urlGetRoles, response.AccessToken); // GET request to API
listRoles.ForEach(role => identity.AddClaim(new Claim(ClaimTypes.Role, role)));
context.HttpContext.User = new ClaimsPrincipal(identity);
context.HandleCodeRedemption(response.AccessToken, response.IdentityToken);
}
While debugging, I noticed all roles are added the the User's Claims list after this line:
context.HttpContext.User = new ClaimsPrincipal(identity);
But, apparently, in my Home controller (that is where the user is being redirected to, after authenticated), when I access HttpContext.User, I can't seem to find any of the roles I added before except for "Admin" (which I'm guessing is a default ClaimTypes.Role value).
[Authorize]
public IActionResult Index()
{
if (User.IsInRole("SomeRole"))
{
return RedirectToAction("SomeAction", "SomeController");
}
else
{
return RedirectToAction("Forbidden", "Error");
}
}
Reading some other posts forums and topics, I found that this is probably a context persistence issue, which I tried to solve with this code in my Account controller:
public async Task Login(string returnUrl = "/")
{
await HttpContext.ChallengeAsync(
"OIDC",
new AuthenticationProperties
{
AllowRefresh = false,
IsPersistent = true,
RedirectUri = returnUrl
});
}
Some examples said that I could use context.Principal.AddIdentity(identity); in order to persist the new Claims list, but then I got the following error:
InvalidOperationException: only a single identity supported
IdentityServer4.Hosting.IdentityServerAuthenticationService.AssertRequiredClaims(ClaimsPrincipal principal)
Summing up, I must find a way to persist the role claims I added to the User's Claims list but I got no success until now.
Update on that, if that's useful to anyone.
I deduced that the problem whas with context.HttpContext.User = new ClaimsPrincipal(identity);, which I understood previously to be the part of the code that handled the persistence of the new claims.
Actually, I did noticed there was a context.Principal attribute of the type ClaimsPrincipal, and looked like it was the actual Current context, so I digged into it and tried to figure out a way to add elements to its "IEnumerable<Claim> Claims" attribute which is readonly.
After a while, I found the following solution that worked just fine for me:
Instead of
var identity = new ClaimsIdentity(context.Principal.Identity);
var listRoles = GenericProxies.RestGet<List<string>>(urlGetRoles, response.AccessToken); // GET request to API
listRoles.ForEach(role => identity.AddClaim(new Claim(ClaimTypes.Role, role)));
I tried
var identity = context.Principal.Identity as ClaimsIdentity;
if(identity != null)
{
var listRoles = GenericProxies.RestGet<List<string>>(urlGetRoles, response.AccessToken); // GET request to API
foreach (var role in listRoles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
}
I'm building a web application which uses the cookie authentication built into ASP.NET Core 2.1.
I have my own sign in method which queries my own custom password verification and setting of claims. Roughly it looks like this:
public async Task<ActionResult<LoginResponse>> DoLogin([FromBody] LoginRequest req)
{
// fetch account and verify password
var claims = new List<Claim>
{
new Claim(ClaimTypes.Sid, account.AccountId.ToString(), ClaimValueTypes.Integer),
new Claim(ClaimTypes.Email, account.EmailAddress, ClaimValueTypes.Email),
new Claim(ClaimTypes.Role, "member", ClaimValueTypes.String)
};
var identity = new ClaimsIdentity(claims, "password");
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return new LoginResponse
{
Success = true
};
}
I would like to conditionally render a "Log Out" button on various parts of the site if the user has the cookie which authenticates the user. Additionally I'd like to fetch the Sid claim so I can deliver personalized messaging on some public parts of the site.
The problem I have is that the way I have been fetching the Sid only works if my controller or controller action has an [Authorize] attribute on it. Without the [Authorize] attribute, the claim is missing.
Code:
public static int? GetNullableAccountId(this ClaimsPrincipal principal)
{
var claim = principal.FindFirst((Claim c) => { return c.Type == ClaimTypes.Sid; });
if (claim == null)
return null;
return int.Parse(claim.Value);
}
// then in the controller I try to get the account id:
var accountId = accessor.HttpContext.User.GetNullableAccountId();
// always null even when I have a valid cookie
I swear that I didn't need the [Authorize] attribute for this to work in prior versions of ASP.NET Core, but I couldn't find anything meaningful in change logs.
Is there some trick to getting ASP.NET Core to build the user identity on all calls or am I taking the wrong approach all together?
It seems it was a silly mistake. I was invoking app.UseAuthentication() after app.UseMvc() when configuring my application builder.
The documentation actually explicitly states the following:
Call the UseAuthentication method before calling UseMvcWithDefaultRoute or UseMvc
Source: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-2.2#configuration
I followed this article (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?tabs=aspnetcore2x) of Microsoft to migrate my Authentication Procedure in my .NET Core 2.0 MVC Application.
Startup.cs (ConfigureServices)
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication("MyCookieAuthenticationScheme")
.AddCookie("MyCookieAuthenticationScheme", options => {
options.AccessDeniedPath = "/Account/Forbidden/";
options.LoginPath = "/Account/Login/";
});
Startup.cs (Configure)
app.UseAuthentication();
AccountController.cs
List<Claim> claims = new List<Claim> {
new Claim(ClaimTypes.Name, "testUser"),
new Claim(ClaimTypes.Email, model.Email),
//new Claim("ID", user.ID.ToString(), ClaimValueTypes.Integer),
new Claim(ClaimTypes.Role, "Admin")
};
ClaimsIdentity identity = new ClaimsIdentity(claims, "MyCookieAuthenticationScheme");
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync("MyCookieAuthenticationScheme", principal, new AuthenticationProperties
{
IsPersistent = false
});
Unfortunately my .NET Cookie is never set. That means User.Identity.IsAuthenticated is always false. I tried many cookie options like changing Cookie.SameSite or Cookie.SecurePolicy to all possible values.
I work with Visual Studio 2017, localhost over https, Chrome 61.
Assuming that you are serving your application on localhost, it seems that the Chrome browser does not set the cookies for IPs or intranet hostnames like localhost. You can serve your application from IIS and use a binding with a valid host name.
I think you should provide login process using by Identity's UserManager class instead of HttpContext.SignInAsync. Inject IUserManager to your controller constructor, and use it to login.
AccountController: Controller
{
private readonly SignInManager<ApplicationUser> _signInManager;
public AccountController(SignInManager<ApplicationUser> singInManager)
{
_signInManager = signInManager;
}
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
...
}
}
You can modify Identity's cookie settings in your Startup.cs. Take a glance:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity
When upgrading our site's authentication system for .NET Core 2.0, I had to update our controller method to use the AuthenticationHttpContextExtensions.SignInAsync() method instead of the old HttpContext.SignInAsync().
Example:
public async Task ClaimsLogin() {
// Claims identity creation here...
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
await Task.FromResult(
AuthenticationHttpContextExtensions.SignInAsync(
this.httpContextAccessor.HttpContext,
"NameOfYourCookieHere",
userPrincipal,
new AuthenticationProperties()
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(2880),
IsPersistent = false,
AllowRefresh = false
}));
}
Hopefully this helps someone!
I was getting the same issue, after trying lot of googling and stackoverflow answers for hours I couldn't log in despite getting success from the SignInManager.
So what I did solve the problem is added
.AspNetCore.Identity.Application in cookie Name manually, and added a random token in the value field then I was able to log in sucessfully. Again after logging out the identity token was gone from the cookie but after login in again it came in the cookie this time.
I hope this process helps some one who has gone through like me.
I setup my HttpListener to allow Basic and Anonymous authentication. I also add a new MessageHandler called AuthenticationHandler to my configuration.
public void Configuration(IAppBuilder appBuilder)
{
var listener = (HttpListener)appBuilder.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemes = AuthenticationSchemes.Basic | AuthenticationSchemes.Anonymous;
// Configure Web API for self-host.
HttpConfiguration config = new HttpConfiguration();
//...
config.MessageHandlers.Add(new AuthenticationHandler());
appBuilder.UseWebApi(config);
}
AuthenticationHandler is a DelegatingHandler. I set up my claims, identity and principal as below:
if (validCredentials)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password),
};
var roles = user.Roles.ToString().Split(',');
// Make sure to add all the user's roles to the claims
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var identity = new ClaimsIdentity(claims, Scheme);
var principal = new ClaimsPrincipal(new [] {identity});
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
HttpContext.Current.User = principal;
}
If I check Thread.CurrentPrincipal in the debugger my principal has my identity and all the claims that I've added. All seems fine inside the DelegatingHandler, however these claims are not making their way to my controller.
In my controller I have a simple [Authorize(Roles="Admin")] attribute on a function. However, if I check the User in the debugger, the only claim listed is the username. So [Authorize] works, as does [Authorize(Users="admin')], however, obviously role based authentication will not work. I could of course look the username up in the database and manually check the roles in the function, but that is clearly not the correct way of going about this. What am I doing wrong?
For OWIN, the principal must be set in the request like so:
request.GetRequestContext().Principal = principal;
I'm writing a Web API with ASP.NET and Identity 2.0 right know. The API should be only accessible, if a user is 'logged in' successfully. The login is working wonderful, but the logout (signout) doesn't seem to work. Here's some code I'm using:
Identity Configuration:
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext<IdentityDbContext<IdentityUser>>(HLAccountManager.CreateDbContext);
app.CreatePerOwinContext<UserManager<IdentityUser>>(HLAccountManager.CreateUserManager);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
GlobalConfiguration.Configuration.SuppressDefaultHostAuthentication();
GlobalConfiguration.Configuration.Filters.Add(new HostAuthenticationFilter("Bearer"));
}
Authentication Controller:
[HttpPost]
[ActionName("Authenticate")]
[AllowAnonymous]
public String Authenticate(JObject data)
{
dynamic json = data;
string user = json.user;
string password = json.password;
if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(password))
return "failed";
var userIdentity = UserManager.FindAsync(user, password).Result;
if (userIdentity != null)
{
var identity = new ClaimsIdentity(IdentityConfig.OAuthBearerOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userIdentity.Id));
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
string AccessToken = IdentityConfig.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
return AccessToken;
}
return "failed";
}
[HttpGet]
[Authorize]
[ActionName("Logout")]
public String Logout()
{
var owinContext = HttpContext.Current.GetOwinContext();
owinContext.Authentication.SignOut(DefaultAuthenticationTypes.ApplicationCookie, DefaultAuthenticationTypes.ExternalBearer);
return "OK";
}
The Authenticate-method works well. My webapp gets a token from the request which I can set as an Authorization-header (e.g. in $http for angular apps). Subsequent calls to a [Authorize]-annotated function will return correctly.
However, if I call Logout, it will return the "OK" string correctly, but doesn't invalidate the token. If I call a Authorize-method after calling Logout, I still get a correct value and not the expected 401 - Unauthorized.
I've seen this post: ASP.Net Identity Logout and tried the Signout without parameters. That doesn't work either.
HttpContext doesn't have the GetOwinContext. It's in HttpContext.Current in my case. Am I doing something wrong?
Why is my Logout Method not working?
It seems like I got the basic concept of (bearer) tokens wrong and that's why it is not working. I leave this here in case somebody stumbles over the same problem:
Tokens can't be revoked or invalidated - at least not with ASP.NET Identity 2.0. The SignOut does not
work for those kinds of authentications.
A solution for this are so called refresh tokens. There's currently no default implementation in Identity 2.0 or OWIN. But I found two blog posts with a solution:
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
http://leastprivilege.com/2013/11/15/adding-refresh-tokens-to-a-web-api-v2-authorization-server/