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.
Related
I have an existing MVC project that uses FormsAuthentication for its authentication.
I need to incorporate the option of logging in with an OpenID IDP in addition to the regular login page already available.
The problem I'm having is challenging the IDP on demand and setting the authentication cookie once the claims are received, I can't find the reason why the cookie is not sticking. The flow seems to be working fine, and I can see the claims in the AuthorizationCodeReceived callback.
Here's the Startup.Auth.cs code:
var notificationHandlers = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (context) =>
{
string username = context.AuthenticationTicket.Identity.FindFirst("preferred_username").Value;
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(60), true, "");
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
context.Response.Cookies.Append(FormsAuthentication.FormsCookieName, encryptedTicket);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
if (context.OwinContext.Request.Path.Value != "/Account/SignInWithOpenId")
{
context.OwinContext.Response.Redirect("/Account/Login");
context.HandleResponse();
}
return Task.FromResult(0);
}
};
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "xxxxxxxxx",
ClientId = "MyClient",
ClientSecret = "xxxxxxxx",
RedirectUri = "http://localhost:52389/",
PostLogoutRedirectUri = "http://localhost:52389/",
ResponseType = "code id_token",
Scope = "openid profile email roles",
UseTokenLifetime = false,
TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "preferred_username",
RoleClaimType = "role"
},
Notifications = notificationHandlers
});
app.SetDefaultSignInAsAuthenticationType("Cookies");
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies",
AuthenticationMode = AuthenticationMode.Passive,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider()
});
app.UseStageMarker(PipelineStage.Authenticate);
And here's the AccountController SignInWithOpenId method:
public ActionResult SignInWithOpenId()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);
// If I don't have this line, reponse redirects to the forms authentication login... so maybe something is wrong here?
return new HttpUnauthorizedResult("IDP");
}
else
{
return RedirectToAction("Index", "Default");
}
}
Any pointers would be greatly appreciated. Thank you.
This is the exact thing I'm trying to do at the moment. I will let you know if I find anything useful.
Update:
I ended up disabling Forms Authentication in the MVC web app. I was doing a proof of concept so it wasn't a hard requirement. I know this was not really what you were getting at. I successfully used my IdP to login and redirect back to the web app. Where the proof of concept ended was the HttpContext.User object was needed to be populated.
I was able to get this, or at least an equivalent, working in .NET 4.7. My use case is that most subscribers are being upgraded to log in via Azure AD B2C, but we have public PCs that we want to authenticate with a manual claim via an obscured URL.
I'm using Microsoft.Owin.Security.OpenIDConnect and related packages, and the Owin startup is standard, although I will point out this line:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
I had to disable Forms authentication entirely; I could not get this working when anything other than Anonymous Authentication was enabled in IIS.
The core of the solution was actually an example I found here: How to use OWIN forms authentication without aspnet identity
/* URL validated, add authenticated claim */
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "PublicPC"),
new Claim(ClaimTypes.Email, "PublicPC#example.org")
};
var id = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
var ctx = HttpContext.Current.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
But critically, I needed to specify CookieAuthenticationDefaults.AuthenticationType, which is what I'm using in the Owin startup.
solved by adding these code to Global.asax:
protected void Application_BeginRequest()
{
Context.Response.SuppressFormsAuthenticationRedirect = true;
}
according to Prevent ASP.NET from redirecting to login.aspx
Summary
This is my first try with OAuth2 and External Login Mechanisms.
I'm creating a WebApp that will expose API features through a user-friendly UI.
In order to make API calls, I need to receive an access token from QBO that grants access to resources.
So, my WebApp has an external login option which I use to authenticate against QBO, and then authorize my app.
Everything works fine until...
Services Configuration
Based on a tutorial for GitHub authentication, I came up with this.
services.AddAuthentication(o => {
o.DefaultAuthenticateScheme = IdentityConstants.ExternalScheme;
o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
o.DefaultChallengeScheme = IdentityConstants.ExternalScheme;
})
.AddOAuth("qbo", "qbo", o => {
o.CallbackPath = new PathString("/signin-qbo");
o.ClientId = Configuration["ecm.qbo.client-id"];
o.ClientSecret = Configuration["ecm.qbo.client-secret"];
o.SaveTokens = true;
o.Scope.Add("openid");
o.Scope.Add("profile");
o.Scope.Add("email");
o.Scope.Add("com.intuit.quickbooks.accounting");
o.AuthorizationEndpoint = Configuration["ecm.qbo.authorization-endpoint"];
o.TokenEndpoint = Configuration["ecm.qbo.token-endpoint"];
o.UserInformationEndpoint = Configuration["ecm.qbo.user-info-endpoint"];
o.Events.OnCreatingTicket = async context => {
var companyId = context.Request.Query["realmid"].FirstOrDefault() ?? throw new ArgumentNullException("realmId");
var accessToken = context.AccessToken;
var refreshToken = context.RefreshToken;
Configuration["ecm.qbo.access-token"] = accessToken;
Configuration["ecm.qbo.refresh-token"] = refreshToken;
Configuration["ecm.qbo.realm-id"] = companyId;
context.Backchannel.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
context.Backchannel.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await context.Backchannel.GetStringAsync(context.Options.UserInformationEndpoint);
var result = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
var user = (ClaimsIdentity)context.Principal.Identity;
user.AddClaims(new Claim[] {
new Claim("access_token", accessToken),
new Claim("refresh_token", refreshToken),
new Claim(ClaimTypes.GivenName, result["givenName"]),
new Claim(ClaimTypes.Surname, result["familyName"]),
new Claim(ClaimTypes.Email, result["email"]),
new Claim(ClaimTypes.Name, result["givenName"]+" "+result["familyName"])
});
};
});
This works. I can add my claims based on user information, the context.Principal.Identity indicates that it's authenticated.
For some reasons, it seems to try and redirect to `/Identity/Account/Login?returnUrl=%2F. Why is that?
Login page redirection
Here, I don't get why I get the redirection and this confuses me a lot. So, I added the AccountController just to try and shut it up.
namespace ecm.backoffice.Controllers {
[Authorize]
[Route("[controller]/[action]")]
public class AccountController : Controller {
[AllowAnonymous]
[HttpGet]
public IActionResult Login(string returnUrl = "/") {
return Challenge(new AuthenticationProperties { RedirectUri = returnUrl });
}
[Authorize]
[HttpGet]
public async Task<IActionResult> Logout(string returnUrl = "/") {
await Request.HttpContext.SignOutAsync("qbo");
return Redirect(returnUrl);
}
}
}
And this creates more confusion than it solves, actually. I'm lost here...
Apply Migrations
This Individual User Authentication WebApp seems to use Identity which looks like it creates a lot of behind the scene mechanisms. I first tried to register to my app, and had to "Apply Migrations", which I totally get, since the data model wasn't initialized.
So, I clicked the Apply Migrations button. I though I was okay with this...
Entity Framework Core
I am aware that the app is using Entity Framework Core for its persistence mechanism, hence the registration process, etc. And to configure it, I needed to add these lines to the services configs.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
It looks like it just won't work at all.
Thoughts
At this point, I think that the message says it Entity Framework just can't load the user information from its underlying data store. I totally understand that, and I don't want to register this user. I just want to take for granted that if QBO authenticated the user, it's fine by me and I grant open bar access to the WebApp features, even if the user ain't registered.
How to tell that to my WebApp?
Related Q/A I read prior to ask
Prevent redirect to /Account/Login in asp.net core 2.2
ASP.NET Core (2.1) Web API: Identity and external login provider
asp.net core 2.2 redirects to login after successful sign in
External Login Authentication in Asp.net core 2.1
And many more...
I have a cookie authentication based core project using the AspNetCore.Authentication.Cookies but I can't seem to make the user to authenticate. I have read similar threads but none of the solutions provided seem useful.
[HttpPost]
public async Task<IActionResult> CookieAuth(ITwitterCredentials userCreds)
{
var claims = new[] {
new Claim("AccessToken" , userCreds.AccessToken),
new Claim("AccessTokenSecret", userCreds.AccessTokenSecret)
};
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "CookieAuthentication"));
await HttpContext.Authentication.SignInAsync("CookieAuthentication", principal);
return Ok();
}
and startup.cs configure method
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "CookieAuthentication",
LoginPath = new PathString("/"),
AccessDeniedPath = new PathString("/"),
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
The user does not seem to authenticate as HttpContext.User.Identity.IsAuthenticated always returns false.
Any idea why this might not be working?
as of .net 2.x, if you're using cookie auth, ensure you include the authenticationScheme, the identity and auth properties.
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, email));
identity.AddClaim(new Claim(ClaimTypes.Name, email));
identity.AddClaim(new Claim(ClaimTypes.Role, "User"));
var principal = new ClaimsPrincipal(identity);
var authProperties = new AuthenticationProperties
{
AllowRefresh = true,
ExpiresUtc = DateTimeOffset.Now.AddDays(1),
IsPersistent = true,
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(principal),authProperties);
return RedirectToPage("dashboard");
Try to clear browser cache and cookies, then retry.
In my case it was options.Cookie.SecurePolicy, our hosted application was not on HTTPS yet.
.AddCookie(config =>
{
config.Cookie.HttpOnly = true;
//options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
config.Cookie.SameSite = SameSiteMode.Lax;
config.Cookie.Name = CookieAuthenticationDefaults.AuthenticationScheme;
config.LoginPath = "/Login";
});
I think your startup configure service missing the following lines of code:
app.UseAuthentication();
app.UseAuthorization();
Usually this happens when you try to run with Http, enable SSL should fix your problem, its in the project Debug section.
As explaned in the previous answers, essentialy you should add Name and Role claims to your new identity. If your HttpContext.SignInAsync method succeeded your HttpContext is now depends on cookies and you are authenticated. I just want to add a very important point about placing middleware handlers in Configure method of the Startup class.
If you try to get your context filled with identity values, do it after your authentication middleware.
Otherwise in your middleware you'll get HttpContext blank (without identity data).
Decorate the class with AuthenticationScheme
Like this:
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
public class LoginController : PageController<LoginPage>
That whas the key for me!
All HttpContext properies magically was set correctly.
I'm currently working on a project that I don't use Identity.
The things is that this project should have a remember me option that allow user to automatically reconnect into the web site.
My problem is that I can't find any complete tutoriel to create a cookie without Identity.
If somebody have a good sample of code or tutoial :)
Thanks
In my project, I use AngularJS for Frontend and .Net Core API for Backend.
So, I don't need to configure pages for AccessDeniedPath, LoginPath and so on.
Here's what I do:
Configure the cookie in the startup class:
public void Configure(IApplicationBuilder app) {
//...
CookieAuthenticationOptions options = new CookieAuthenticationOptions();
options.AuthenticationScheme = "MyCookie";
options.AutomaticAuthenticate = true;
options.CookieName = "MyCookie";
app.UseCookieAuthentication(options);
//...
}
The login is like this:
[HttpPost, Route("Login")]
public IActionResult LogIn([FromBody]LoginModel login) {
//...
var identity = new ClaimsIdentity("MyCookie");
//add the login as the name of the user
identity.AddClaim(new Claim(ClaimTypes.Name, login.Login));
//add a list of roles
foreach (Role r in someList.Roles) {
identity.AddClaim(new Claim(ClaimTypes.Role, r.Name));
}
var principal = new ClaimsPrincipal(identity);
HttpContext.Authentication.SignInAsync("MyCookie", principal).Wait();
return Ok();
}
The logout is like this:
[HttpPost, Route("Logout")]
public async Task<IActionResult> LogOut() {
await HttpContext.Authentication.SignOutAsync("MyCookie");
return Ok();
}
Then you can use it like this:
[HttpPost]
[Authorize(Roles = "Role1,Role2,Role3")]
public IActionResult Post() {
//...
string userName = this.User.Identity.Name;
//...
}
*See that the method is authorized only for "Role1, Role2 and Role3". And see how to get the user name.
I think you are asking how to make a persistent cookie when the user logs in with a "Remember Me?" checkbox selected.
All the answers are on the right path - you'll ultimately invoke HttpContext.Authentication.SignInAsync, but the cookie middleware issues a session cookie by default. You'll need to pass along authentication properties as a third parameter to make the cookie persistent, for example:
HttpContext.Authentication.SignInAsync(
Options.Cookies.ApplicationCookieAuthenticationScheme,
userPrincipal,
new AuthenticationProperties { IsPersistent = isPersistent });
There is a pretty good article on this here: Using Cookie Middleware without ASP.NET Core Identity.
Basically what you do is set up the cookie handling middleware as if you were going to identify the user, but then you just create a ClaimsPrincipal object without asking the user to login. You pass that object to the SigninAsync method and it creates the cookie for you. If you follow this article you should be fine.
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.