Asp.net Core Persistent Authentication - Custom Cookie Authentication - asp.net-core

I'm trying to get a persistent connection so the users only have to use their password once. I've used this doc: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?tabs=aspnetcore2x but the users still get disconnected after a while.
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
principal,
new AuthenticationProperties
{
IsPersistent = true
});
What can I do to get a really persistent connection ?

The persistence granted by IsPersistent is, according to the docs, only meant to imply that the authentication will persist through browsing sessions (that is, it is kept even when the browser is closed). You need a combination of Persistence and to set an expiration time for the cookie. The expiration of the cookie can be set via the CookieAuthenticationOptions (MSDN), using the ExpireTimeSpan option.
Without persistence, expiration of the authentication can be set using the ExpiresUtc option in AuthenticationOptions,

There are few things you should notice when implement persistent cookie authentication.
Configure sliding expiration for cookie in Startup.cs. It will be clearer if you explicitly set values that you needed and don't use default settings.
private void ConfigureAuthentication(IServiceCollection services)
{
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
// true by default
options.SlidingExpiration = true;
// 14 days by default
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
});
}
When user check flag "Remember Me" configure cookie to persist across browser sessions and set absolute expiration (as long as you want). This settings will override SlidingExpiration and ExpireTimeSpan. In login action:
List<Claim> claims = new List<Claim>();
// Prepare user claims...
var userIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
AuthenticationProperties authenticationProperties = new AuthenticationProperties() { IsPersistent = model.RememberMe };
if (model.RememberMe)
{
// One month for example
authenticationProperties.ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(1);
}
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, authenticationProperties);
Confugure data protection. Remember machineKey in old classic asp.net webforms. Otherwise cookie will be reset after every IIS app pool restart. You should configure data protection before authentication in Startup.cs. To store keys in root folder of your app:
private void ConfigureDataProtection(IServiceCollection services, IWebHostEnvironment environment)
{
var keysDirectoryName = "Keys";
var keysDirectoryPath = Path.Combine(environment.ContentRootPath, keysDirectoryName);
if (!Directory.Exists(keysDirectoryPath))
{
Directory.CreateDirectory(keysDirectoryPath);
}
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keysDirectoryPath))
.SetApplicationName("YourApplicationName");
}
From docs:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-5.0
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-5.0

Related

Blazor Server cookie validation

I am building a Blazor Server application, which authenticates to AD and issues a cookie used for authentication and authorization (few AD claims are stored in the cookie).
Is there anything "special" I need to add to ensure the cookie was not tampered?
I've already added this part to handle cookie expiration.
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(15);
options.Cookie.MaxAge = options.ExpireTimeSpan;
options.SlidingExpiration = true;
});
When a user logs in, we are building a ClaimsPrincipal which adds some AD groups as claims:
var claimIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var claimsPrincipal = new ClaimsPrincipal(claimIdentity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, new()
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(15),
RedirectUri = Request.Host.Value
});
For example, some users are getting special privileges based on a claim stored in the cookie, we need to validate if the cookie was tampered or not.

ASP.NET Core 3.1 Use both OpenIDConnect and Custom Cookie Authentication

I have an existing application that makes use of Cookie Authentication, and would like to add the ability to authenticate users using Active Directory. The current application uses Cookie based authentication and custom authorisation - roles in a database.
I am adding bits from example located here:
Add sign-in with Microsoft to an ASP.NET Core web app
When I run the application I get an error:
System.InvalidOperationException: Scheme already exists: Cookies
What is the correct way to configure OpenIdConnect and Cookie Authentication.
// STEP 1 Basic Cookie Auth
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Auth";
options.AccessDeniedPath = "/Home/AccessDenied";
options.Cookie.IsEssential = true;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromSeconds(day/2.0);
options.Cookie.HttpOnly = true; // not accessible via JavaScript
options.Cookie.Name = "login_token";
options.TicketDataFormat = new CustomJwtDataFormat(
SecurityAlgorithms.HmacSha256,
tokenValidationParameters);
});
// STEP 2 OpenID Connect Auth
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), "OpenIdConnect", "Cookies", true);
I am not able to find any examples using both Cookie Authentication and OpenID Connect. Is this possible? Allowing users to login selectively using Active Directory authentication, or local authentication (details stored in local database).
After changing the "Cookie" name, get's rid of the error message,
but breaks the local authorisation, e.g.
When a valid Username and Password is given, I typically
authorise the login.
HttpContext.Response.Cookies.Append("login_token", token, GetCookieOptions());
Currently with OpenIDConnect configured User.Identity.IsAuthenticated
remains false.
According to the error messages, it tell you that you have multiple Scheme which named cookies.
According to the AddMicrosoftIdentityWebApp Method document, you could find the third parameter name is the cookieScheme.
The cookie-based scheme name to be used. By default it uses "Cookies".
But you have already set this name at above, so you should use other one. For example: "ADCookies".
Like below:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), "OpenIdConnect", "ADCookies", true);
The mixed approach is a minefield but the below is allowing use to Authenticate Users via IdentityServer4 using OIDC while authenticating the Application into AzureAD with Identity.Web to get tokens for Api calls.
services.AddAuthentication(options =>
{
options.DefaultScheme = "IS4Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("IS4Cookies")
.AddOpenIdConnect("oidc", "OpenID Connect", options =>
{
options.SignInScheme = "IS4Cookies";
// Get IdentityServer configuration from appsettings.json.
var config = Configuration.GetSection("IdentityServerOptions").Get<IdentityServerOptions>();
options.Authority = config.Authority;
options.RequireHttpsMetadata = false;
options.ClientId = config.ClientId;
options.ClientSecret = config.ClientSecret;
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.MapJsonKey("role", "role");
options.ClaimActions.MapJsonKey("role", System.Security.Claims.ClaimTypes.Role);
options.ClaimActions.MapJsonKey("email", "email");
options.ClaimActions.MapJsonKey("preferred_username", "preferred_username");
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.Response.Redirect("/");
context.HandleResponse();
return Task.FromResult(0);
}
};
})
.AddMicrosoftIdentityWebApp(Configuration, "AzureOptions")
.EnableTokenAcquisitionToCallDownstreamApi(new string[]{"sms.all" })
.AddInMemoryTokenCaches();
This is what I use and it works, you just need to specify the configureCookieAuthenticationOptions and set the name inside there and you should be good to go, also I had to use lax for SameSite or it would not work for me.
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(identityOptions =>
/* {identityOptions.ClientId ="";}, // if you want to specify the options manually instead of Configuration.GetSection() call*/
Configuration.GetSection("AzureAd"),
configureCookieAuthenticationOptions: authCookie => { // Setup SSO cookie
authCookie.Cookie.Name ="Your.Cookie.Name.Here";// change name to hide .net identifiers in name
authCookie.Cookie.HttpOnly = true;// make so client cannot alter cookie
authCookie.Cookie.SecurePolicy = CookieSecurePolicy.Always;// require https
authCookie.Cookie.SameSite = SameSiteMode.Lax;// from external resource
// verify options are valid or throw exception
authCookie.Validate();
}
);
You may or may not need all of the authCookie values here, but it should get you started in the right direction!
It's possible to mix two mechanisms.
I use MicrosoftIdentity authentication for access to administration web pages and cookies authentication for my APIs and SignalR hubs.
I use this in startup ConfigureServices
services
.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie("CookiesApiScheme", options =>
{
options.SlidingExpiration = true;
// There is no redirection to a login page for APIs and SignalR Hubs, I just made a call to /Api/Login/SignIn with credential
options.AccessDeniedPath = new PathString("/Api/Login/AccessDenied"); // Action who just returns an Unauthorized
})
.AddMicrosoftIdentityWebApp(Configuration); // By default scheme is "CookieAuthenticationDefaults.AuthenticationScheme"
And in API controller you can use something like this
[Route("api/[controller]")]
[ApiController]
[Authorize(Roles = Roles.ADMIN)]
[Authorize(AuthenticationSchemes = "CookiesApiScheme")]
public class DefaultController : ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
Based on this post: ASP.NET Core 2.0 AzureAD Authentication not working

Unable to expire a .NetCore cookie

I'm using .NetCore and Cookie authentication. Account management (login, logout, etc) is all managed through an API as opposed to using the Identity UI. My understanding is that the server can't actually send anything to the client to actually remove the cookie, rather, I would need to "expire" the cookie on the server and return it to the client.
So here is my setup
Startup.cs
services.AddAuthentication("mycookie")
.AddCookie("mycookie", options => {
options.Cookie.HttpOnly = true;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.Cookie.Expiration = TimeSpan.FromMinutes(30);
});
In my ApiController I have the following:
AccountController.cs
[HttpGet("signout")]
public async Task<IActionResult> SignOut()
{
var temp = new AuthenticationProperties()
{
ExpiresUtc = DateTime.Now.AddDays(-1)
}
await HttpContext.SignOutAsync("mycookie",temp);
return Ok();
}
However, my cookie never seems to expire when I call signout. I noticed in the browser dev console, the cookie says Expires on: Session. I would've expected to see a date/time there.
How do I get rid of the cookie or expire it when signout is called?
use the Clear-Site-Data response header. Among other things, it allows you to clear cookies on the client's browser.
In my example below, I'm clearing the user's cache and cookies for my domain.
public IActionResult Logout()
{
this.HttpContext.Response.Headers.Add("Clear-Site-Data", new StringValues(new string[] { "\"cache\"", "\"cookies\"" }));
return Ok();
}
First be sure to check browser support for this header.
If your browser does not support Clear-Site-Data, then you should be able to expire them like so:
this.Response.Cookies.Append("cookieName", "deleted", new CookieOptions()
{
Expires = DateTimeOffset.MinValue
});
When this logout response returns to the browser, I'm able to see the cookies disappear (using Chrome 73.0.3683.86, Developer Tools/Storage/Cookies/{myDomain} )

Where to store JWT Token in .net core web api?

I am using web api for accessing data and I want to authenticate and authorize web api.For that I am using JWT token authentication. But I have no idea where should I store access tokens?
What I want to do?
1)After login store the token
2)if user want to access any method of web api, check the token is valid for this user,if valid then give access.
I know two ways
1)using cookies
2)sql server database
which one is the better way to store tokens from above?
Alternatively, if you just wanted to authenticate using JWT the implementation would be slightly different
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var user = context.Principal.Identity.Name;
//Grab the http context user and validate the things you need to
//if you are not satisfied with the validation fail the request using the below commented code
//context.Fail("Unauthorized");
//otherwise succeed the request
return Task.CompletedTask;
}
};
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey("MyVeryStrongKeyHiddenFromAnyone"),
ValidateIssuer = false,
ValidateAudience = false
};
});
still applying use authentication before use MVC.
[Please note these are very simplified examples and you may need to tighten your security more and implement best practices such as using strong keys, loading configs perhaps from the environment etc]
Then the actual authentication action, say perhaps in AuthenticationController would be something like
[Route("api/[controller]")]
[Authorize]
public class AuthenticationController : Controller
{
[HttpPost("authenticate")]
[AllowAnonymous]
public async Task<IActionResult> AuthenticateAsync([FromBody]LoginRequest loginRequest)
{
//LoginRequest may have any number of fields expected .i.e. username and password
//validate user credentials and if they fail return
//return Unauthorized();
var claimsIdentity = new ClaimsIdentity(new Claim[]
{
//add relevant user claims if any
}, "Cookies");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await Request.HttpContext.SignInAsync("Cookies", claimsPrincipal);
return Ok();
}
}
in this instance I'm using cookies so I'm returning an HTTP result with Set Cookie. If I was using JWT, I'd return something like
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]LoginRequest loginRequest)
{
//validate user credentials and if they validation failed return a similar response to below
//return NotFound();
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("MySecurelyInjectedAsymKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
//add my users claims etc
}),
Expires = DateTime.UtcNow.AddDays(1),//configure your token lifespan and needed
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey("MyVerySecureSecreteKey"), SecurityAlgorithms.HmacSha256Signature),
Issuer = "YourOrganizationOrUniqueKey",
IssuedAt = DateTime.UtcNow
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
var cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(4);//you can set this to a suitable timeframe for your situation
cookieOptions.Domain = Request.Host.Value;
cookieOptions.Path = "/";
Response.Cookies.Append("jwt", tokenString, cookieOptions);
return Ok();
}
I'm not familiar with storing your users tokens on your back end app, I'll quickly check how does that work however if you are using dotnet core to authenticate with either cookies or with jwt, from my understanding and experience you need not store anything on your side.
If you are using cookies then you just need to to configure middleware to validate the validity of a cookie if it comes present in the users / consumer's headers and if not available or has expired or can't resolve it, you simply reject the request and the user won't even hit any of your protected Controllers and actions. Here's a very simplified approach with cookies.(I'm still in Development with it and haven't tested in production but it works perfectly fine locally for now using JS client and Postman)
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "yourCookieName";
options.Cookie.SameSite = SameSiteMode.None;//its recommended but you can set it to any of the other 3 depending on your reqirements
options.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents
{
OnRedirectToLogin = redirectContext =>//this will be called if an unauthorized connection comes and you can do something similar to this or more
{
redirectContext.HttpContext.Response.StatusCode = 401;
return Task.CompletedTask;
},
OnValidatePrincipal = context => //if a call comes with a valid cookie, you can use this to do validations. in there you have access to the request and http context so you should have enough to work with
{
var userPrincipal = context.Principal;//I'm not doing anything with this right now but I could for instance validate if the user has the right privileges like claims etc
return Task.CompletedTask;
}
};
});
Obviously this would be placed or called in the ConfigureServices method of your startup to register authentication
and then in your Configure method of your Startup, you'd hookup Authentication like
app.UseAuthentication();
before
app.UseMvc()

ASP.NET Core 2 MVC Cookie based authentication. Are there more options?

I have ASP.NET Core 2 MVC project with Cookie based authentication. Code I have attached below. Right now I don't have any problems but I am looking to improve it or any other options that I also need to explore. More Secure. I really need direction.
Removed some unnecessary code.
Question:
1 - Am I doing it right. Anything to add more?
2 - Are there more options for authentication. More secure?
After Successfull Credentials
var result = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);
if (result.Succeeded)
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role);
identity.AddClaim(new Claim(ClaimTypes.Email, model.Email));
identity.AddClaim(new Claim(ClaimTypes.Name, model.Email));
identity.AddClaim(new Claim("Admin", "Admin"));
identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
// Authenticate using the identity
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { IsPersistent = false });
if (Type == "Admin")
return RedirectToAction(nameof(AdminController.Index), "Admin");
return RedirectToAction(nameof(AccountController.Index), "Account");
}
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie();
services.AddAuthorization(options =>
{
options.AddPolicy("admin-policy", x => { x.RequireClaim("Admin"); });
});
On Controller
[Authorize]
[Authorize(Policy = "admin-policy")]
When it comes to a website-style application, no. Cookie auth is really the best you've got. HTTP requests are idempotent. To access a protected endpoint, the client (in this case, a web browser) must send something along with the request that either serves to authenticate or simply identify the client has having already been authenticated. That's where the cookie comes in. There's other ways this can be done, but principally, browsers don't really support those workflows.
With a different client, particularly clients where you have control over how the request is formatted, JWTs would probably be your best bet. However, again, that doesn't help you with a web browser. It does mean, though, that can go cookieless for things like AJAX or HttpClient requests, though, since, again, you can control the headers there.