Add revokeable tokens to user profiles
The plugin should provide a way for a "secret" to be associated with each user that can be refreshed or revoked at any time, invalidating existing sessions.
This unique per-user "secret" should be stored in usermeta and should be queryable and mutable by GraphQL queries.
A change to the token should invalidate any previously provided tokens, meaning the user would need to re-authenticate before making any further requests that require authentication.
The per-user secret should be part of the JWT token that's issued. When a JWT token is issued, if the authenticating user doesn't already have a user secret, one should be created for them. Additionally, if a jwtUserSecret is queried for on a specific user, and one doesn't yet exist, one should be created.
If a users jwtUserSecret has been revoked, then no secret should be created when queried or mutated, invalidating all tokens they have previously been issued and preventing a valid token from being issued while the user's secret remains in a revoked state.
There should be a UI to view the "secret", refresh the "secret", revoke the "secret", or un-revoke the "secret". There should also be an option in the UI to set the expiration for the Token, overriding the default expiration, allowing individual users to have longer-lived tokens, such as the ContentShare user.
The GraphQL query for the token should look something like:
{
user {
jwtUserSecret #String!
}
}
The GraphQL mutations for the token should look something like:
Refresh JWT User Secret
The refreshUserJWTSecret should only be mutable by users with proper capabilities to do so.
mutation refreshUserJWTSecret{
updateUser( input: {
id: "..."
clientMutationId: "..."
refreshUserJWTSecret: true
}) {
user {
id
jwtUserSecret # Should return the user's new secret
jwtAuthToken # Should return a valid token that can be used for Authenticated requests
}
}
}
Revoke JWT User Secret
The isJWTUserSecretRevoked should only be mutable by users with proper capabilities to do so.
mutation revokeUserJWTSecret {
updateUser( input: {
id: "..."
clientMutationId: "..."
isJWTUserSecretRevoked: true
}) {
user {
id
jwtUserSecret # Should return null
jwtAuthToken # Should return null
}
}
}
Un-revoke JWT User Secret
The isJWTUserSecretRevoked should only be mutable by users with proper capabilities to do so.
mutation unRevokeUserJWTSecret {
updateUser( input: {
id: "..."
clientMutationId: "..."
isJWTUserSecretRevoked: false
}) {
user {
id
jwtUserSecret # Should return the token
jwtAuthToken # Should return a valid JWT that can be used for future requests
}
}
}
@jasonbahl Not sure what you mean with User secrets, but as of my understanding a refresh token should be this secret, which is associated with the user and it should be revokable. So it would be good to have something like revokeJwtAuthToken which a User can call, if he wants to "logout" from all session. Meaning, that the refresh token, that might be saved by another client, can't be reused, as it is then invalid, which forces the every client to login again.
So, the thing that makes a Refresh Token different than an Auth Token is a "secret" or a "key" that is stored in the users user_meta.
This piece of meta is encoded into the Refresh Token, and if it ever changes in the user meta, the Refresh Token is invalidated.
At the moment you can revoke a token like so:
mutation REVOKE_TOKEN {
updateUser(input: {clientMutationId: "RevokeToken", id: "dXNlcjox", revokeJwtUserSecret: true}) {
clientMutationId
user {
...UserFields
}
}
}
And unrevoke a token like so:
mutation UNREVOKE_TOKEN {
updateUser(input: {clientMutationId: "RevokeToken", id: "dXNlcjox", revokeJwtUserSecret: true}) {
clientMutationId
user {
...UserFields
}
}
}
It might (probably) makes more sense to have these be independent Root Mutations though.
You can see these mutations in action here:
