Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refresh token using FastAPI and Swagger

I am trying to create an API for our organization using FastAPI. It has a KeyCloak server that is used for all authentication, and OpenID Connect and JWTs in the way that is considered best practice.

In the simplest case, someone else takes care of acquiring a valid JWT token so that FastAPI then can simply decode and read the user and permissions.

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_current_user(token: str = Depends(oauth2_scheme)):

    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )

    try:
        jwt_token = jwt.decode(token, key=env.keycloak_server_public_key, audience='myorg')
        return jwt_token['preferred_username']
    except jwt.exceptions.ExpiredSignatureError:
        raise credentials_exception

Life is simple!

I do, however, want to try to let users explore the API using the Swagger page. I have created this function that lets users login using the UI:

@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    login_request = requests.post(
        "https://mygreatorg.com/auth/realms/master/protocol/openid-connect/token",
        data={
            "grant_type": "password",
            "username": form_data.username,
            "password": form_data.password,
            "client_id": "fastapi-application",
        },
    )
    raw_response = json.loads(login_request.content.decode('utf-8'))
    raw_response['acquire_time'] = time.time()

    TOKEN_CACHE[form_data.username] = raw_response

    return {"access_token": raw_response['access_token'], "token_type": "bearer"}

This works fine. The auth header in Swagger is now the token, and it validates, for about a minute. The expire time for the tokens is set to a very short time. One is then expected to refresh them using the refresh_token provided in the raw_response payload.

I can very easily make another request to get a new valid access token given the refresh_token. But I am unable to get Swagger to change the token of the request in the UI. The only way I find is to log out and log in again, but users will be very annoyed if they only allow a minute without being kicked out.

One workaround would be to simply cache the token and ignore the expiration time and let the user be logged in for a while longer, but that defeats the purpose of the entire security setup and feels like a bad idea.

Any ideas on how to let the UI of FastAPI update the bearer token when it needs a refresh, without letting the user log in again?

like image 659
hirolau Avatar asked Sep 16 '20 10:09

hirolau


People also ask

How do I trigger a refresh token?

To use the refresh token, make a POST request to the service's token endpoint with grant_type=refresh_token , and include the refresh token as well as the client credentials if required.

Are refresh tokens JWTS?

The API returns a short-lived token (JWT), which expires in 15 minutes, and in HTTP cookies, the refresh token expires in 7 days. JWT is currently used for accessing secure ways on API, whereas a refresh token generates another new JWT access token when it expires or even before.

Can I use refresh token multiple times?

A refresh token doesn't expire until the new access token obtained from it was executed in an API call. When the new access token has been used, the refresh token that was used to obtain it automatically expires.


1 Answers


This is far from an answer and I will likely delete this later. It is a only a placeholder for outlining my research into this question


USE CASE: Swagger UI need to auto refresh with updated JWT token without closing the UI.

Systems/Applications:

  • KeyCloak
  • FastApi
  • Swagger
  • OpenID Connect and JWTs

When I looking into this question I noted that the issue in this question was raised in the issues at both FastApi and Swagger.

Swagger


When looking through the code base of Swagger, I noted an authorization parameter named persistAuthorization. According to the documentation this parameter will maintain authorization data and this data will not be lost on browser close/refresh.

In the Swagger code base I see this item:

  # source: /src/plugins/topbar/topbar.jsx
  #
  flushAuthData() {
    const { persistAuthorization } = this.props.getConfigs()
    if (persistAuthorization)
    {
      return
    }
    this.props.authActions.restoreAuthorization({
      authorized: {}
    })
  }

The code above makes calls to /src/core/plugins/auth/actions.js.

In the Swagger pull requests there is a pending feature named configs.preserveAuthorization This scope of this feature:

Refreshing or closing/reopening the the page will preserve authorization if we have configs.preserveAuthorization set to true.

It's unclear based on the comments how the features preserveAuthorization and persistAuthorization are different.

FastApi


When I was looking at the FastApi code base, I noted a OAuthFlowsModel named refreshUrl. I looked through the FastApi document and didn't see this mentioned.

# source: /fastapi/security/oauth2.py
#
class OAuth2AuthorizationCodeBearer(OAuth2):
    def __init__(
        self,
        authorizationUrl: str,
        tokenUrl: str,
        refreshUrl: Optional[str] = None,
        scheme_name: Optional[str] = None,
        scopes: Optional[Dict[str, str]] = None,
        auto_error: bool = True,
    ):
        if not scopes:
            scopes = {}
        flows = OAuthFlowsModel(
            authorizationCode={
                "authorizationUrl": authorizationUrl,
                "tokenUrl": tokenUrl,
                "refreshUrl": refreshUrl,
                "scopes": scopes,
            }
        )
        super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)

    async def __call__(self, request: Request) -> Optional[str]:
        authorization: str = request.headers.get("Authorization")
        scheme, param = get_authorization_scheme_param(authorization)
        if not authorization or scheme.lower() != "bearer":
            if self.auto_error:
                raise HTTPException(
                    status_code=HTTP_401_UNAUTHORIZED,
                    detail="Not authenticated",
                    headers={"WWW-Authenticate": "Bearer"},
                )
            else:
                return None  # pragma: nocover
        return param

Looking through the issues for FastApi I noted one asking for OAuth2RerefreshRequestForm to be added. The scope of this issue is token refresh.

I also noted another issue refresh token with Keycloak and Fastapi


I apologize for not being able to provide a solid answer.

like image 60
Life is complex Avatar answered Sep 20 '22 09:09

Life is complex