    def save_bearer_token(self, token, request, *args, **kwargs):
        """Persist the Bearer token.
        The Bearer token should at minimum be associated with:
            - a client and it's client_id, if available
            - a resource owner / user (request.user)
            - authorized scopes (request.scopes)
            - an expiration time
            - a refresh token, if issued
            - a claims document, if present in request.claims
        The Bearer token dict may hold a number of items::
            {
                'token_type': 'Bearer',
                'access_token': 'askfjh234as9sd8',
                'expires_in': 3600,
                'scope': 'string of space separated authorized scopes',
                'refresh_token': '23sdf876234',  # if issued
                'state': 'given_by_client',  # if supplied by client
            }
        Note that while "scope" is a string-separated list of authorized scopes,
        the original list is still available in request.scopes.
        The token dict is passed as a reference so any changes made to the dictionary
        will go back to the user.  If additional information must return to the client
        user, and it is only possible to get this information after writing the token
        to storage, it should be added to the token dictionary.  If the token
        dictionary must be modified but the changes should not go back to the user,
        a copy of the dictionary must be made before making the changes.
        Also note that if an Authorization Code grant request included a valid claims
        parameter (for OpenID Connect) then the request.claims property will contain
        the claims dict, which should be saved for later use when generating the
        id_token and/or UserInfo response content.
        :param token: A Bearer token dict
        :param request: The HTTP Request (oauthlib.common.Request)
        :rtype: The default redirect URI for the client
        Method is used by all core grant types issuing Bearer tokens:
            - Authorization Code Grant
            - Implicit Grant
            - Resource Owner Password Credentials Grant (might not associate a client)
            - Client Credentials grant
        """
        log_token = {}
        log_token.update(token)
        # redact sensitive keys in log
        for key in ('access_token', 'refresh_token', 'state'):
            if key in token:
                value = token[key]
                if isinstance(value, str):
                    log_token[key] = 'REDACTED'
        app_log.debug("Saving bearer token %s", log_token)

        if request.user is None:
            raise ValueError(f"No user for access token: {request.user}")
        client = (
            self.db.query(orm.OAuthClient)
            .filter_by(identifier=request.client.client_id)
            .first()
        )
        # FIXME: support refresh tokens
        # These should be in a new table
        token.pop("refresh_token", None)

        # APIToken.new commits the token to the db
        orm.APIToken.new(
            oauth_client=client,
            expires_in=token['expires_in'],
            scopes=request.scopes,
            token=token['access_token'],
            session_id=request.session_id,
            user=request.user,
        )
        return client.redirect_uri
Persist the Bearer token.
The Bearer token should at minimum be associated with:
- a client and it's client_id, if available
- a resource owner / user (request.user)
- authorized scopes (request.scopes)
- an expiration time
- a refresh token, if issued
- a claims document, if present in request.claims
The Bearer token dict may hold a number of items::
{
'token_type': 'Bearer',
'access_token': 'askfjh234as9sd8',
'expires_in': 3600,
'scope': 'string of space separated authorized scopes',
'refresh_token': '23sdf876234',  # if issued
'state': 'given_by_client',  # if supplied by client
}
Note that while "scope" is a string-separated list of authorized scopes,
the original list is still available in request.scopes.
The token dict is passed as a reference so any changes made to the dictionary
will go back to the user.  If additional information must return to the client
user, and it is only possible to get this information after writing the token
to storage, it should be added to the token dictionary.  If the token
dictionary must be modified but the changes should not go back to the user,
a copy of the dictionary must be made before making the changes.
Also note that if an Authorization Code grant request included a valid claims
parameter (for OpenID Connect) then the request.claims property will contain
the claims dict, which should be saved for later use when generating the
id_token and/or UserInfo response content.
:param token: A Bearer token dict
:param request: The HTTP Request (oauthlib.common.Request)
:rtype: The default redirect URI for the client
Method is used by all core grant types issuing Bearer tokens:
- Authorization Code Grant
- Implicit Grant
- Resource Owner Password Credentials Grant (might not associate a client)
- Client Credentials grant
jupyterhub/oauth/provider.py