Invalidate all authentication cookies in ASP.net CORE 3 - asp.net-core

On ASP.net CORE 3, when a user logout, I would like to invalidate all the cookies that exist on different devices. The user might have logged in from several different browsers, and the user has the option to use "Remember me" that lasts 30 days.
My understanding to solve this problem so far:
Use a securityStamp (a GUID) that I store in the database at the user level
Add this securityStamp in the Claims at login
When logout => change the securityStamp in the database
When http request arrives on a method of controller with [Authorize] attribute, check if the securityStamp match the one stored in the database. If not, redirect to login page.
My question is about point 4) where and how write this securityStamp check in the ASP.net CORE framework and redirect to login page ?
Here is my code at login time
string securityStamp = Guid.NewGuid().ToString();
saveSecurityStampInDB(securityStamp, user.Id);
var userClaims = new List<Claim>()
{
new Claim("id", user.Id.ToString()),
new Claim("securityStamp", securityStamp),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string")
};
var grantMyIdentity = new ClaimsIdentity(userClaims, "User Identity");
var userPrincipal = new ClaimsPrincipal(new[] { grantMyIdentity });
if (rememberMe.HasValue && rememberMe.Value)
{
await HttpContext.SignInAsync(userPrincipal, new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMonths(1)
});
}
else
{
await HttpContext.SignInAsync(userPrincipal);
}
UPDATE:
I have my own user table, I don't use entityFramework and the whole built-in Identity management.

You can use the SecurityStamp Property and the SecurityStampValidatorOptions.ValidationInterval Property to make the logout user's cookie invalid.
1.Register ValidationInterval in ConfigureServices
services.Configure<SecurityStampValidatorOptions>(options =>
{
options.ValidationInterval = TimeSpan.FromSeconds(1);//set your time
});
2.Add userManager.UpdateSecurityStampAsync()in your Logout like below
public async Task<IActionResult> Logout()
{
var userid = userManager.GetUserId(User);
var user = await userManager.FindByIdAsync(userid);
await userManager.UpdateSecurityStampAsync(user);
await signInManager.SignOutAsync();
return RedirectToAction("Index", "Home");
}
Result:

Related

User.Identity.IsAuthenticated AND _signInManager.IsSignedIn(User) return always null / fasle IN MVC CORE 6 2022

I have a new asp.net MVC core 6 application .try to authenticate users ( not by using Identity scaffolding ) .. however the the SignInmanger is always return False
Login function
programe.cs
Full code snippet for login :
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginVM loginvm)
{ // this wil return view model
if (!ModelState.IsValid)
{
return View(loginvm);
}
var user = await _userManager.FindByEmailAsync(loginvm.Username);
if (user != null)
{
// if we have user let us check the password
var checkpsssword = await _userManager.CheckPasswordAsync(user, loginvm.Password);
if (checkpsssword)
{
var letUserLoginIn = await _signInManager.PasswordSignInAsync(user, loginvm.Password, false, false);
if (letUserLoginIn.Succeeded)
{
var tempo = User.Identity.IsAuthenticated;
var isok = _signInManager.IsSignedIn(User);
ViewBag.tempo=tempo;
ViewBag.isok = isok;
return RedirectToAction("index", "Movie");
}
ModelState.AddModelError("Error","can login innnnn");
TempData["Error"] = "Password is not correct! !";
return View(loginvm);
}
else
{
// password wrong
TempData["Error"] = "Password is not correct! !";
}
}
TempData["Error"] = "no user found ya mozznoz!";
return View(loginvm);//STRONGLY TYPED VIEW
}
One part #Kevin have mentioned above, and another part was the missing of authentication mechanism register.
It should be something like builder.Services.AddAuthentication(opts => opts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
AddAuthentication part add all the necessary middlewares and config to setup authentication process. Here, we specify DefaultScheme as CookieAuthenticationDefaults.AuthenticationScheme
AddCookie tell asp.net Core that we want to store the login information in cookie, therefore, a response that tell client to save a cookie with pre-defined information was sent(and the name for that authentication mechanism of choice was default to CookieAuthenticationDefaults.AuthenticationScheme).
For every subsequent requests, the cookie was included then server know, we already logged in

How to invalidate the session after log out in IdentityServer4

We have website with SSO using IdentitySrver4. We recently tested our site for security and we found one vulnerability which is as follow,
A session token for the application remained valid (and could be used
to authenticate requests to the application) even after the logout
function had been invoked in the associated session. This indicated
that the session termination mechanism was not fully effective and
increased the possibility of unauthorised access to the application.
It should be noted that the tokens did have an effective time out
after a period of time. The logout function terminated the associated
session client-side (by removing the session cookie from the user’s
browser) but the session remained valid server-side. Requests which
were made after the logout function had been used, but which provided
the original session cookie, continued to be successful.
Following are code snippets,
IdentityServer
StartUp ConfigureServices:-
services.AddIdentityServer(.......
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opt=> {
opt.ExpireTimeSpan = TimeSpan.FromMinutes(Convert.ToInt32(Configuration["CookieTimeOut"]));
//This has time limit of 30 minutes
})
.AddOpenIdConnect("oidc", opts =>
{.......
The Login code is as follow,
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, cp);
LogOut method in IdentityServer:-
[HttpGet]
public async Task<IActionResult> Logout(string clientId, string returnUrl, string culture)
{
var clientsList = new List<Client>();
// delete authentication cookie
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var cookiesToBeDeleted = Request.Cookies.Keys;
foreach (string cookie in cookiesToBeDeleted)
{
Response.Cookies.Delete(cookie);
}
var logoutURL = _configuration["DefaultLogoutRedirectUrl"];
if (Uri.IsWellFormedUriString(returnUrl, UriKind.Absolute))
{
//TODO: validate that the return url belongs to the client who has initiated the logout request
//if the url validation fails then we should return to a pre-determined url that is mentioned in the config
logoutURL = returnUrl;
}
var vm = new LoggedOutViewModel()
{
PostLogoutRedirectUri = logoutURL,
SignOutUrls = _clients.Value
.Where(client => !string.IsNullOrWhiteSpace(client.FrontChannelLogoutUri))
.Select(client => client.FrontChannelLogoutUri),
ClientName = clientId,
AutomaticRedirectAfterSignOut = true
};
//If there is no return url then display a local logged out page
return View("LoggedOut", vm);
}
[HttpGet]
public IActionResult LoggedOut(string returnUrl)
{
var vm = new LoggedOutViewModel()
{
PostLogoutRedirectUri = returnUrl,
SignOutUrls = null,
AutomaticRedirectAfterSignOut = true
};
return View(vm);
}
We have used FrontChannel logouts,Here all the client's "FrontChannelLogoutUri" are rendered in "IFrame" in logout page of the Identity Server.
Client Code (MVC App):-
StartUp ConfigureServices:-
services.AddAuthentication(options =>
{
options.DefaultScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, ck =>
{
ck.Cookie.Name = "ClientCookie";
ck.ExpireTimeSpan = TimeSpan.FromMinutes(Convert.ToInt32(Configuration["CookieTimeOut"]));
//This also has value of 30 minutes.
})
.AddOpenIdConnect("oidc", opts =>
{.......
LogOut function in Client App :-
public async Task<IActionResult> Logout(string path)
{
var logoutUrl = //This is IdentityServer LogOut Method URL
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var prop = new Microsoft.AspNetCore.Authentication.AuthenticationProperties()
{
RedirectUri = logoutUrl
};
await HttpContext.SignOutAsync("oidc", prop);
return Redirect(logoutUrl);
}
The FrontChannel method for Client App:-
[AllowAnonymous]
public async Task<IActionResult> ForcedSignout()
{
var cookiesToBeDeleted = Request.Cookies.Keys;
foreach (string cookie in cookiesToBeDeleted)
{
Response.Cookies.Delete(cookie);
}
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return View();
}
The logout workflow is as follow:-
When logout is called from Client MVC App, we have called both
HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme) & HttpContext.SignOutAsync("oidc", prop).
Then user is redirected to the IdentityServer Logout method, which again calls the HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme).
When Identity's logout page is rendered we generate the "IFrames" with Client's "FrontChannelLogoutUri" (in this case "ForcedSignout()" of Client App).
"ForcedSignout" method again deletes the cookies and call HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme).
Steps to replicate the issue:-
We captured the Client App's edit method in postman.
The postman request with changed data was run and it worked.
After that user was logged out of the Client App and the postman request was again run which ran successfully even though we have logged out.
Then we waited for 30 minutes (Inactivity) and tried the postman request but this time it did not work as the sessions had timed out.
We need to know, how can we remove/invalidate the session from server when the user logs out of the application? Thank you.

IdentityServer4 authenticating against an external api

We have a requirement to authenticate users in IdentityServer4 against an external API. The scenario works like this:
User visits a Javascript client application and clicks the login button to redirect to IdentityServer login page (exact same client as provided in the docs here
User enters their username (email) and password
IdentityServer4 connects to an external API to verify credentials
User is redirected back to the JavaScript application
The above process works perfect when using the TestUsers provided in the QuickStarts. However, when an API is used, the login page resets and does not redirect the user back to the JavaScript client. The only change is the below code and a custom implementation of IProfileService.
Below is the custom code in the login action (showing only the relevant part):
var apiClient = _httpClientFactory.CreateClient("API");
var request = new HttpRequestMessage(HttpMethod.Post, "/api/auth");
var loginModel = new LoginModel
{
Email = model.Email,
Password = model.Password
};
var content = new StringContent(JsonConvert.SerializeObject(loginModel),
Encoding.UTF8, "application/json");
request.Content = content;
HttpResponseMessage result = await apiClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
var loginStatus = JsonConvert.DeserializeObject<ApiLoginStatus>(
await result.Content.ReadAsStringAsync());
if (loginStatus.LoginSuccess)
{
await _events.RaiseAsync(new UserLoginSuccessEvent(model.Email, model.Email, loginStatus.Name, clientId: context?.ClientId));
AuthenticationProperties props = null;
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
var user = new IdentityServerUser(loginStatus.SubjectId)
{
DisplayName = loginStatus.Name
};
await HttpContext.SignInAsync(user, props);
if (context != null)
{
if (await _clientStore.IsPkceClientAsync(context.ClientId))
{
return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
}
return Redirect(model.ReturnUrl);
}
The code actually hits the return View() path, but for some reason it resets and the login page is shown again.
Code in Startup.cs:
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddProfileService<ProfileService>()
.AddDeveloperSigningCredential();
Code in ProfileService.cs:
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var profile = await GetUserProfile(context.Subject.GetSubjectId());
var claims = new List<Claim>
{
new Claim(ClaimTypes.Email, profile.Email),
new Claim(ClaimTypes.Name, profile.Name)
};
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
var profile = await GetUserProfile(context.Subject.GetSubjectId());
context.IsActive = (profile != null);
}
There are multiple sources online that show how to user a custom store for authentication, but they all seem to use ResourceOwnerPasswordValidator. If someone could point out what is missing here, it would help greatly. Thanks.
So the issue turned out to be very simple. We had missed removing the builder.AddTestUsers(TestUsers.Users) line when setting up IdentityServer in Startup.cs.
Looking at the code here, it turned out that this line was overriding our profile service with the test users profile service. Removing that line solved the problem.

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()

How to use authentication from my ASP.NET Core site to authenticate angular 2 web app?

I have ASP.NET Core app with angular 2 front-end. I use cookie auth.
But I want to split my app into 2 separate sites - one front-end site on angular2 and one back-end site on asp.net core.
How do I use auth from ASP.NET Core site to authenticate front-end app?
There's a login page in my back-end site. How do I identify in front-end app that I'm not authenticated, then redirect to back-end app and then get auth cookies? I'm not sure I understand mechanic of this process.
I used token based authentication. I choosed this solution: https://stormpath.com/blog/token-authentication-asp-net-core & https://github.com/nbarbettini/SimpleTokenProvider
For Authentication I prefer to use cookies.
Use cookie authentication without Identity
Login Code
[HttpPost("login")]
[AllowAnonymous]
public async Task<HttpBaseResult> Login([FromBody]LoginDto dto)
{
var user = db.Users.Include(u=>u.UserRoles).SingleOrDefault();
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName)
};
var roles = user.UserRoles.Select(u => u.Role);
foreach (var item in roles)
{
claims.Add(new Claim(ClaimTypes.Role, item.Name));
}
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
new ClaimsPrincipal(identity),
new AuthenticationProperties { IsPersistent = dto.RememberMe });
// ...
}
Cross Domain
ConfigureServices
{
options.SlidingExpiration = true;
options.Cookie.HttpOnly = false;
// Dynamically set the domain name of the prod env and dev env
options.Cookie.Domain = Configuration["CookieDomain"];
});
Configure
app.UseCors(builder => builder.WithOrigins("http://localhost:4200", "http://www.example.com","http://example.com")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
Angular Code
public login(userName: string, password: string, rememberMe: boolean): Observable<HttpBaseResult> {
const url: string = `${this.url}/login`;
var data = {
UserName: userName,
Password: password,
RememberMe: rememberMe
};
return this.client.post<HttpBaseResult>(url, data, { withCredentials: true });
}