8

I have an implementation of Identity Server 4 that uses Entity Framework Core for persistent storage and ASP.NET Core Identity for users management. Since this IDS will support public applications, we were asked to add a way of completely blocking users - which means not allowing them to sign in and remove their existing logins.

After long research, I've determined that IDS does not support anything like expiring Access Tokens, since that's not part of OpenID Connect. What strikes me as completely odd is that I switched a client to use Reference Tokens, which are correctly stored in the PersistedGrants table, but even clearing that table doesn't invalidate future requests, as the user is still authenticated both to the client application and to Identity Server itself.

Is there any store/service I can re-implement to block all access from a given logged in user?

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
  • How did you determine that there are no support for expiry in access tokens? Out of the box JWT's that IDS4 issues are short-lived access tokens with an option to also get a refresh token. Also, if you are using reference tokens, then your api resources that are protected by IDS4 need to have introspection enabled, – Vidmantas Blazevicius Mar 12 '19 at 18:22
  • @VidmantasBlazevicius I didn't say that access tokens do not expire, but that you cannot force them to expire – Camilo Terevinto Mar 12 '19 at 18:24
  • Ah, fair enough, that makes sense, can you explain how these requests are still valid if you are using reference tokens? What type of apps are your client and your api? – Vidmantas Blazevicius Mar 12 '19 at 18:36
  • @VidmantasBlazevicius For this first test, I'm using an Angular client with the OIDC-JS library against 2 ASP.NET Core API Resources, one of them hosted inside the IDS application – Camilo Terevinto Mar 12 '19 at 18:41
  • Ok, so your Angular client stores the reference token and uses it in the api request header. Then your Api resource should try and introspect the reference token every time by using introspection endpoint `/connect/introspect`. Can you check IDS4 logs and verify that this is happening? Reference token should be useless without introspection endpoint telling that its valid and exchanging it for access token. – Vidmantas Blazevicius Mar 12 '19 at 18:47
  • @VidmantasBlazevicius Hmm, I don't see any log for `/connect/introspect` in the IDS logs (and I do have a lot of logs there, Verbose set to minimum level). Anything I might be missing? – Camilo Terevinto Mar 12 '19 at 18:55
  • You should have at least a trace log for this (https://github.com/IdentityServer/IdentityServer4/blob/e56727e4079d9455af5ff60e575718c9de382e35/src/Endpoints/IntrospectionEndpoint.cs#L57). I would even check your web server logs to ensure introspection is happening. – Vidmantas Blazevicius Mar 12 '19 at 18:58
  • @VidmantasBlazevicius Thanks for pointing me in the right direction. Someone had changed the AccessTokenType back to 0 because I had configured only one of the APIs. I configured the other one, re-changed it to 1 and started seeing calls to the retrospection endpoint. Deleting the PersistedGrant now DOES invalidate the token. – Camilo Terevinto Mar 12 '19 at 19:27
  • @CamiloTerevinto - also check the token caching period on the API. Usually it's configured to not do a round-trip de-reference of the token for every API call (as that's an unnecessary performance hit), but more typically perhaps once an hour. It's configured in `.AddIdentityServerAuthentication(x => x.EnableCaching/CacheDuration)` on the API side. Even if you remove the row from PersistedGrants the API will not bother de-referencing the token and continue to trust the claims during this caching period. – sellotape Mar 12 '19 at 20:30
  • @CamiloTerevinto it is a pity that I deleted my question. I just wanted let you know.it might be interesting for future readers. – asdf_enel_hak Sep 26 '20 at 14:38

2 Answers2

1

You'll have to clear the cookies as well.

But you may want to investigate a different approach. Where IdentityServer is used as an authentication service and authorization is outsourced, like the PolicyServer.

That way you can opt-in authorization, making it less important that a user is still authenticated.

  • I cannot clear cookies because 1) there aren't always cookies (JWT tokens don't need to be stored in cookies) and 2) I am an admin blocking an user, not the user blocking themselves. But I'll investigate the second point – Camilo Terevinto Mar 12 '19 at 17:55
  • @CamiloTerevinto It depends on the client. As long as the client thinks the user is authorized and doesn't verify with IdentityServer, there isn't much you can do. This is the responsibility of the client. But when it comes to access tokens, you can use reference tokens or short-lived JWT's, where there is a small window that the user has still access to the resource. But as soon as the token needs to be renewed, access can be denied. –  Mar 12 '19 at 18:23
  • "...you can use reference tokens or short-lived JWT's..." the title of my question refers explicitly to reference tokens, and the body explains why I switched from Access Tokens to Reference Tokens. Shouldn't these always check against IDS? – Camilo Terevinto Mar 12 '19 at 18:29
  • At some point but not all the time. Suppose I have a website that uses cookies only (and without some background api), then where do I need an access token for? The user remains authorized until the cookie expires. When I add an api then the user can access the api only for as long as the access token lives. If it is not refreshed, access is denied, check [this](https://stackoverflow.com/questions/54498454/still-logged-in-mvc-site-but-cant-call-web-api) question. From the docs: The API receiving this reference must then open a back-channel communication to IdentityServer to validate the token. –  Mar 12 '19 at 18:41
  • As [documented](http://docs.identityserver.io/en/latest/topics/reference_tokens.html) the API should verify the token, but when calling a client (as a resource), then the client should add it's own implementation (e.g. middleware) to verify the token. –  Mar 12 '19 at 18:55
  • You can use server-side cookie storage linked to the user entity - easy to revoke the cookies then too. There's built in support in .net core for this using the ITicketStore interface. Check out CookieAuthenticationOptions.SessionStore. – mackie Mar 12 '19 at 19:00
1

In the end, the problem was that someone had changed AccessTokenType from 1 back to 0 since another API didn't work, as it was configured for using Access Tokens rather than Reference Tokens. Thanks @VidmantasBlazevicius for pointing in the right direction of looking at logs for calls to the connect/introspect endpoint.

For reference, this is the code we ended up using (called by admin users, appropriately secured):

[HttpPut("{userId}/Block")]
public async Task<IActionResult> BlockUser(string userId)
{
    var user = await _context.Users.FindAsync(userId);

    if (user == null || !user.LockoutEnabled || user.LockoutEndDate > DateTime.Now)
    {
        return BadRequest();
    }

    var currentTokens = await _context.PersistedGrants
        .Where(x => x.SubjectId == user.UserId)
        .ToArrayAsync();

    _context.PersistedGrants.RemoveRange(currentTokens);

    var newLockOutEndDate = DateTime.Now + TimeSpan.FromMinutes(_options.LockOutInMinutes);
    user.LockoutEndDate = newLockOutEndDate;

    string updater = User.Identity.Name;
    user.UpdateTime(updater);

    await _context.SaveChangesAsync();

    return NoContent();
}
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120