3

I know how to implement a route to register a user and also how to trade user credentials in for an access token. These are both covered in the official tutorial.

How do you invalidate the access token (and refresh token) for a registered user. This is necessary both for logging out and for limiting damage if a user's account is compromised.

I see there is a method

authServer.revokeAllGrantsForResourceOwner(identifier)

but I am still working on how to get the identifier from the user since the client app knows the username but not the user id in the server database. It would be nice to just pass in the current token and have the server cancel all the tokens for that user.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393

1 Answers1

3

If you want to revoke all tokens given a token, grab the user ID from the authorization token and run a delete query for that user's tokens:

class TokenManagerController extends ResourceController {
  @Operation.delete()
  Future<Response> deleteTokens() async {
    final userId = request.authorization.ownerID;

    final query = Query<ManagedAuthToken>(context)
      ..where((token) => token.resourceOwner).identifiedBy(userId);
    final count = await query.delete();

    return Response.ok({"userId": userId, "tokensDeleted": count});
  }
}

And make sure you link an authorizer:

router.route("/tokens")
      .link(() => Authorizer.bearer(authServer))
      .link(() => TokenManagerController(context));

FWIW, I recommend having a scope specifically for this action that is only granted for this scenario through an additional login. The UX is that the user has to enter their password again.

If you just want to delete one token, just run a delete query where access_token = the token in the authorization header.

class LogoutController extends ResourceController {
  @Operation.delete()
  Future<Response> deleteTokens(@Bind.header('authorization') String authHeader) async {

    final parser = AuthorizationBearerParser();
    final userToken = parser.parse(authHeader);

    final query = Query<ManagedAuthToken>(context)
      ..where((token) => token.accessToken).equalTo(userToken);
    final count = await query.delete();

    final userId = request.authorization.ownerID;
    return Response.ok({"userId": userId, "tokensDeleted": count});
  }
}
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
Joe Conway
  • 1,566
  • 9
  • 8
  • That's very helpful. Thank you. I may not be understanding the UX, though. It isn't common behavior to have to enter one's password before logging out. That makes me think that the above code is for a more exceptional situation than just logging out. Is that true? To log out maybe all I need to do is delete the token on the client side. – Suragch May 22 '19 at 15:52
  • The answer [here](https://stackoverflow.com/a/37272472/3681880) was helpful but I am still a little unsure whether I should be deleting the tokens on the Aqueduct server on logout. To some extent the Aqueduct server isn't RESTful (in my understanding) since it does save user token information. – Suragch May 22 '19 at 16:36
  • Ah, if you just want to delete one token, just run a delete query where access_token = the token in the authorization header. I'm not sure I follow on the 'RESTful' part; whether the token is stored somewhere or it is a stateless token (e.g. JWT) has no impact on the statelessness of the API. The application is still stateless; you can create any number of instances and they all behave the same and you can use a token you received from one instance of your API with another instance. If storing data in a database made your API stateful, no application would be stateless. – Joe Conway May 22 '19 at 16:50
  • Ok, I was able to do that with `..where((token) => token.accessToken).equalTo(userToken);`. Is the bearer token in the request exposed anywhere? It's not in `request.authorization` apparently. To get the `userToken` I had to do `@Bind.header('authorization')` and then used `AuthorizationBearerParser` to get the token. – Suragch May 22 '19 at 17:55
  • One last question. I can't see a practical difference between deleting just the current token and all the old tokens. Is there any? – Suragch May 22 '19 at 18:11
  • Yes, the difference is that tokens are granted to multiple clients. You might have a token granted to your browser app, but you also might have a long-lived token granted for a service account and your mobile app. I would avoid the terminology 'current token' - there can be multiple 'current tokens'. – Joe Conway May 28 '19 at 16:04
  • That makes sense. Thanks. – Suragch May 28 '19 at 17:50
  • Regarding my question about getting the bearer token, I added a section to your answer for what I understood you to be saying. Please let me know if there is a more direct way to get the bearer token. – Suragch May 28 '19 at 18:08