Reputation: 83
We've recently implemented the ability to disable users in our application with an "Active" boolean field in the Identity.AspNetUsers
table. Logging in to the back office system (an Angular application) is easily handled with an implicit flow -- simply check the field before calling PasswordSignInAsync
.
We can't figure a way to stop a token being issued for any mobile devices using the sister application (written in Flutter) that calls the built in ID Server 4 endpoint /connect/token
. Likewise, we can't stop the application from requesting, and then receiving, a valid refresh token. We can't hard delete the user as we have hard links to other tables in the database for auditing purposes.
Any help would be massively appreciated.
We're using DotNET Core 3,1.
EDIT: We're using the password grant type.
Upvotes: 1
Views: 1502
Reputation: 83
When using the password grant with the built in /connect/token
endpoint, you implement the interface ICustomTokenRequestValidator
and add it as a Transient to the service collection. This has one method, ValidateAsync
, and if the user referenced by your request is valid you simply return
and the pipeline continues as normal. If your user is not valid you set the Result.IsError
property on CustomTokenRequestValidationContext
to true, and supply a string to Result.Error
before you return so the token is then not issued.
Inject UserManager<T>
and IHttpContextAccessor
so you can access the username and user store from the method.
Here's an implementation:
public class CustomTokenRequestValidator : ICustomTokenRequestValidator
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IHttpContextAccessor _httpContextAccessor;
private const string errorMessage = "invalid_username_or_password";
public CustomTokenRequestValidator(
UserManager<ApplicationUser> userManager
, IHttpContextAccessor httpContextAccessor)
{
_userManager = userManager;
_httpContextAccessor = httpContextAccessor;
}
public async Task ValidateAsync(CustomTokenRequestValidationContext context)
{
_httpContextAccessor.HttpContext.Request.Form.TryGetValue("username", out var userOut);
var u = userOut.ToString();
if(u != null)
{
var user = await _userManager.FindByEmailAsync(u);
if(user == null || !user.Active)
{
context.Result.IsError = true;
context.Result.Error = errorMessage;
}
} else
{
context.Result.IsError = true;
context.Result.Error = errorMessage;
}
return;
}
}
Upvotes: 1
Reputation: 19931
When the client asks for a new access token using the refresh token, then the RefreshTokenService is involved. by Customizing refresh token behavior you can lookup if the user is disabled and then reject thew new access token from being issued. See this page for more details about how to do this.
Alternatively you can in the class that implements IPersistedGrantStore add some code to lookup if the user is disabled and then return
return Task.FromResult<PersistedGrant>(null!);
When blocked.
Upvotes: 1