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/
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
Asp.Net Web API with .net core is updating the password automatically on JWT token generation.
So firstly, I had an MVC5 application with asp net membership tables, and wanted to create an API for the same with .net core.
And to support both MVC5 Web APP and WEB API. I added four more columns for AspNetUsers table (ConcurrencyStamp, LockoutEnd, NormalizedEmail, NormalizedUserName).
Although I'm able to get JWT token without any issues, it's also updating the password each time I generate the JWT token which is not allowing users to login from MV5 web APP.
Below is the JWT generate token code
[Route("login")] // /login
[HttpPost]
public async Task<ActionResult> Login([FromBody] LoginViewModel
model)
{
try
{
var user = await
_userManager.FindByNameAsync(model.Username);
if (user != null && await
_userManager.CheckPasswordAsync(user, model.Password))
{
var claim = new[] {
new Claim(JwtRegisteredClaimNames.Sub, user.Id)
};
var signinKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:SigningKey"]));
int expiryInMinutes =
Convert.ToInt32(_configuration["Jwt:ExpiryInMinutes"]);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Site"],
claims: claim,
audience: _configuration["Jwt:Site"],
expires: DateTime.UtcNow.AddMinutes(expiryInMinutes),
signingCredentials: new SigningCredentials(signinKey,
SecurityAlgorithms.HmacSha256)
);
return Ok(
new
{
token = new
JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo,
userName = user.UserName
});
}
return Unauthorized();
}
catch (Exception ex)
{
return Unauthorized();
}
}
Please let me know how to stop updating the PasswordHash and SecurityStamp column in AspNetUsers on generating JWT token.
Update: CheckPasswordAsync(used in web API) method is updating the password field and PasswordSignInAsync method is used in web app
#KirkLarin, thanks a lot and it helped me to solve the problem by adding the below code in StartUp.cs file under Configure service method
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PasswordHasherOptions>(options => options.CompatibilityMode =
PasswordHasherCompatibilityMode.IdentityV2);
}
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'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.