How can we force log-out a user? - asp.net-core

We have a .NET Core WEB API Project that uses Microsoft Identity. We are using this template.
We want managers to log out users manually by using dashboard. How can we do this? How can we cancel their token etc. ?
I applied the solution given by #MdFaridUddinKirom in comments. I've managed to change the security stamp value for a user, but user still be able to use its token and make requests and our api's accepted it not returned 401 error.
The code in UserService.cs
public async Task<bool> UpdateSecurityStampAsync(string email, CancellationToken cancellationToken)
{
try
{
var user = await _userManager.Users
.Where(u => u.Email == email)
.FirstOrDefaultAsync(cancellationToken);
await _userManager.UpdateSecurityStampAsync(user);
return await Task.FromResult(true);
}
catch (Exception ex)
{
return await Task.FromResult(false);
}
}
The code in the controller:
[HttpPost("update-security-stamp")]
[OpenApiOperation("Update security stamp of a user.", "")]
[ApiConventionMethod(typeof(FSHApiConventions), nameof(FSHApiConventions.Register))]
public Task<bool> UpdateSecurityStampAsync(string email, CancellationToken cancellationToken)
{
return _userService.UpdateSecurityStampAsync(email, cancellationToken);
}
Api works and generates 200 response, then I checked database if the user's security stamp value is really change, I saw it is changed. But user still uses api's like nothing happened.

Related

How to use Auth0 for full signup-signin process

I am currently having troubles using auth0.com to set up whole authentication process for my asp.net web api project (I didn't write any view part, cause I'm using it only to learn auth/authoriz).
Their quickstart guides and docs are starting from obtaining a token for your application, I don't understand what is this token, does it grants an access to whole application or what? I wrote a default implementation with creating a user as an object, then generating a token and assigning it to user, then you pass user's email and password and log. I want to do the same using auth0.com
Is there a COMPLETE step-by-step guide on using auth0.com, with the explanation on how to create a user, how to let user log in etc.?
My default implementation:
private readonly UserManager<AppUser> _userManager;
private readonly TokenService _tokenService;
public AccountController(UserManager<AppUser> userManager, TokenService tokenService)
{
_tokenService = tokenService;
_userManager = userManager;
}
[AllowAnonymous]
[HttpPost("login")]
public async Task<ActionResult<UserDTO>> Login(LoginDTO loginDTO)
{
var user = await _userManager.FindByEmailAsync(loginDTO.Email);
if (user is null) return Unauthorized();
var result = await _userManager.CheckPasswordAsync(user, loginDTO.Password);
if (result)
{
return CreateUserObject(user);
}
return Unauthorized();
}
[AllowAnonymous]
[HttpPost("register")]
public async Task<ActionResult<UserDTO>> Register(RegisterDTO registerDTO)
{
if (await _userManager.Users.AnyAsync(x => x.UserName == registerDTO.Username))
{
ModelState.AddModelError("username", "Username taken");
return ValidationProblem();
}
if (await _userManager.Users.AnyAsync(x => x.Email == registerDTO.Email))
{
ModelState.AddModelError("email", "Email taken");
return ValidationProblem();
}
var user = new AppUser
{
DisplayName = registerDTO.DisplayName,
Email = registerDTO.Email,
UserName = registerDTO.Username
};
var result = await _userManager.CreateAsync(user, registerDTO.Password);
if (result.Succeeded)
{
return CreateUserObject(user);
}
return BadRequest(result.Errors);
}
[Authorize]
[HttpGet]
public async Task<ActionResult<UserDTO>> GetCurrentUser()
{
var user = await _userManager.FindByEmailAsync(User.FindFirstValue(ClaimTypes.Email));
return CreateUserObject(user);
}
private UserDTO CreateUserObject(AppUser user)
{
return new UserDTO
{
DisplayName = user.DisplayName,
Image = null,
Token = _tokenService.CreateToken(user),
Username = user.UserName
};
}
In general, you don't need to set up a sign-in/sign-up infrastructure with Auth0. The platform provides you with the Universal Login page where users can register or log in to your application.
The result of the authentication on the Auth0 side is one or two tokens that tell you some info about the user (ID token) and optionally what the user/application is allowed to do (access token).
To learn more about these tokens and the difference between them, read this article.
In your case, since your application is an API, you don't have to deal with user authentication directly. Your API isn't meant for users but for client applications.
To manage users, you can do it through the Auth0 Dashboard. If you want to create your own dashboard to manage users, you can do it through the Auth0 Management API. This is the library to use for .NET.
You assume that a client will call your API endpoints with the proper authorization expressed by an access token.
Take a look at this article for a basic authorization check for ASP.NET Core Web APIs, and this one for a permission-based approach. The articles also show how to test your protected API.
I hope these directions may help.

ASP.NET Core Identity: how to implement 2fa with redirect?

ASP.NET Core Identity offers built-in 2fa providers such as SMS, phone and Authenticator app. They all trigger a 2fa flow in the backend (send an SMS, start a phone call, or just ask the user for a code from their auth app). However, we're required to use an external 2fa provider whose process involves redirecting the user to a page on their URL, which will then redirect back to a page on our end which verifies the 2fa token. This is not an OAuth flow.
IUserTwoFactorTokenProvider only offers a way to return a string token. I could abuse this by returning a URL and then redirecting in the UI but that seems a misuse of the interface. Still, I'd like to have ASP.NET Identity in charge of deciding which users require 2fa, but that means it also wants to do the 2fa itself.
public class ExternalTokenProvider : IUserTwoFactorTokenProvider<ApplicationUser>
{
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<ApplicationUser> manager, ApplicationUser user)
{
return true; //all users require 2fa
}
public async Task<string> GenerateAsync(string purpose, UserManager<ApplicationUser> manager, ApplicationUser user)
{
var challenge = GenerateChallengeToken();
return $"https://2fa.example.com?user={user.Id}&token={challenge}";
}
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<ApplicationUser> manager, ApplicationUser user)
{
//validate the response token;
}
}
Looking at the implementation of AuthenticatorTokenProvider I see it simply returns string.Empty in GenerateAsync(), and then in Microsoft.AspNetCore.Identity.UI the LoginWith2fa.cshtml.cs page is called. I could do something similar but use the URL retrieved from GenerateAsync() to redirect the user.
I've also read How to register a Two-factor authentication provider which still just uses the basic functionality of sending a code via phone/e-mail. It seems the only choice you really have here is to choose the communication gateway.
Is this a proper way to do it within ASP.NET Identity? What's a better way to do this?
My implementation ended up not generating the redirect URL in the TokenProvider (because it doesn't seem to be possible to retrieve the generated token/URL from the frontend) but rather copying the implementation of Microsoft.AspNetCore.Identity.UI and just doing it in the frontend:
MfaTokenProvider.cs
public async Task<bool> CanGenerateTwoFactorTokenAsync(...)
{
return true;
}
public async Task<string> GenerateAsync(...)
{
return Task.FromResult(string.Empty);
}
public async Task<bool> ValidateAsync(...)
{
return _mfaProvider.ValidateToken(token, user.Email);
}
AccountController.cs
[HttpPost]
public async Task<IActionResult> Login(LoginForm form)
{
var result = await _signInManager.PasswordSignInAsync(form.Username, form.Password, form.RememberMe, false);
if (result.RequiresTwoFactor)
{
var authenticationUrl = _mfaProvider.GetUrl(form.Username, form.ReturnUrl);
return RedirectResult(authenticationUrl);
}
return RedirectResult(form.ReturnUrl);
}
This has the advantage of leaving RequiresTwoFactor managed by Identity. The downside is the implementation of the 2FA is split over 2 classes and can cause issues when needing to offer multiple 2FA options. This is not currently a requirement of my app, however.

ASP.NET Core 3.1 Microsoft Graph Access Token lost when application is restarted

Using ASP.NET Core 3.1 and Microsoft Graph API, when the application is restarted the Access Token is lost. The user is still logged in, but is unable to perform any Graph API calls.
I have tried the recommendations mentioned here Managing incremental consent and conditional access but was unable to refresh the token, and resume automatically.
Here is my Controller:
readonly ITokenAcquisition tokenAcquisition;
private GraphServiceClient graphServiceClient;
public HomeController(GraphServiceClient graphServiceClient, ITokenAcquisition tokenAcquisition)
{
this.graphServiceClient = graphServiceClient;
this.tokenAcquisition = tokenAcquisition;
}
[HttpGet]
[AuthorizeForScopes(Scopes = new string[] {"user.read"})]
public async Task<IActionResult> A()
{
User user;
try
{
var scopes = new string[] { "user.read" };
var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
user = await graphServiceClient.Me.Request().GetAsync();
}
catch (MicrosoftIdentityWebChallengeUserException ex)
{
// token is invalid....
// the throw causes a redirect to the User Login Page
throw ex.MsalUiRequiredException;
}
user = await graphServiceClient.Me.Request().GetAsync();
Serilog.Log.Debug("{#User}", user);
return View();
}
In the code above, when the application is restarted the access token is
lost, and re-throwing the exception causes a redirect to the Login-Page.
If I then click the Sign-in with Microsoft button, the user is already signed-in,
there is no need to enter the credentials. If I then access the controller calling the Graph API, the API calls succeed.
How can I refresh the tokens before calling the API?
Also how can I debug this? If I set a breakpoint at throw ex.MsalUiRequiredException; It is of no use, I cannot see,
where the redirect get's it's value from.
The Annotation [AuthorizeForScopes(Scopes = new string[] {"user.read"})]
Is responsible for dealing with the redirect.
For debugging, use the AuthorizeForScopes source file.
In this specific case the AuthenticationScheme was not set to a value,
and was null.
Annotate the Action method to enforce a specific Scheme.
e.g. AuthenticationScheme=OpenIdConnectDefaults.AuthenticationScheme
[HttpGet]
[AuthorizeForScopes(Scopes = new string[] {"user.read"}, AuthenticationScheme=OpenIdConnectDefaults.AuthenticationScheme)]
public async Task<IActionResult> A()
{
// snipped
}
This resulted into the desired redirect path:
https://login.microsoftonline.com/{}/oauth2/v2.0/authorize?client_id={}

.Net Core cannot post to Logout page after cookie expiration

When I create a new Asp.Net Core web application and
services.ConfigureApplicationCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromSeconds(5);
});
then I get logged out after 5 seconds. However, I still have the Logout button on my page at that time, if I click on the button, I get a 400 Bad Request back.
I have then tried removing the expiration code, logging out through the logout button and then using Postman to post to the logout post action but I get the same result.
[AllowAnonymous]
public class LogoutModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<LogoutModel> _logger;
public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
public void OnGet()
{
}
//When I am logged out (whether by clicking on the logout button or after cookie expiration),
//and try to invoke this action, I get a 400 Bad Request back
public async Task<IActionResult> OnPost(string returnUrl = null)
{
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
if (returnUrl != null)
{
return LocalRedirect(returnUrl);
}
else
{
return Page();
}
}
}
Does anyone know what is going on here?
EDIT: BTW, I am using .Net Core 2.2
All posts are protected by anti-forgery tokens out of the box. The anti-forgery token, when a user is logged in, is actually tied to that particular user. Since the user's authentication expires so quickly, pretty much by the time the page has loaded, the token is already invalid. It was created for a particular user, but that user is no longer authenticated. Therefore, the token validation will fail on the post request being made to logout, and you get a 400.
One quick fix would be to apply the [IgnoreAntiforgeryToken] attribute to your LogoutModel. That's technically removing a layer of security, but I can't see much that can be gained by a malicious bad actor forging a request to a logout endpoint.
However, this is mostly an artificial issue. You probably cranked down the auth cookie expiry to test what happens when it expires, but 5 seconds is not a realistic expiry. With that, a user would have to virtually reauthenticate with every request, which means they'd never really get past the login page. As soon as they logged in, they'd be expired, and have to log in again. With a more realistic number like 20 minutes, the likelihood that a user is going to sit on the same page for 20+ minutes, and then attempt to click "Logout" is rather low, making this mostly a non-issue.
On Layout page check for session expiration and call LogOff Action in Account Controller
Below script in Layout.cshtml section and add sessiontimeout in Appsettings.json
<script>
//session end
var sessionTimeoutWarning = #Configuration.GetSection("Cookie")["sessionTimeout"] - 1;
var sTimeout = parseInt(sessionTimeoutWarning) * 60 * 1000;
setTimeout('SessionEnd()', sTimeout);
function SessionEnd() {
window.location = "/Account/LogOff";
}
</script>
In the Account Controller add LogOff Action
public ActionResult LogOff()
{
_signInManager.SignOutAsync();
return RedirectToAction("Index", "Home");
}

IdentityServer4 Handling Logout Correctly

I have a IndentityServer4 project which handles the login functionality and a seperate MVCClient, I need there to be a logout function from the MVC client however looking at the sames one simply has this (MVC client):
public async Task Logout()
{
await HttpContext.Authentication.SignOutAsync("Cookies");
await HttpContext.Authentication.SignOutAsync("oidc");
}
but in the identityserver4 project there is a more complex logout which seems to do a lot more:
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public async Task<IActionResult> Logout(LogoutViewModel model)
{
var vm = await _account.BuildLoggedOutViewModelAsync(model.LogoutId);
if (vm.TriggerExternalSignout)
{
string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
try
{
// hack: try/catch to handle social providers that throw
await HttpContext.Authentication.SignOutAsync(vm.ExternalAuthenticationScheme,
new AuthenticationProperties { RedirectUri = url });
}
catch (NotSupportedException) // this is for the external providers that don't have signout
{
}
catch (InvalidOperationException) // this is for Windows/Negotiate
{
}
}
// delete authentication cookie
await _signInManager.SignOutAsync();
return View("LoggedOut", vm);
}
Can someone explain the logic what is really required on the client.
The first Logout method is used in the MVC client.
The second code belongs to the IdentityServer service.
The first Logout initializes some state for the logout process and redirects to the Logout view on IdentityServer (if you look at the samples there are two Logouts in the IdentityServer AccountController code: one for the logout verification view and one POST handler).