question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[QUESTION] OAuth2: how to store the access_token in a cookie

See original GitHub issue

First check

  • I used the GitHub search to find a similar issue and didn’t find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google “How to X in FastAPI” and didn’t find any information.

Description

How can I save the access_token in a cookie in case of OAuth2 ?

When we do return {"access_token": access_token, "token_type": "bearer"} and we use the OAuth2PasswordBearerWithCookie as oauth2_scheme everything works based on OpenAPI client.

@app.post("/token", response_model=Token)
async def login_for_access_token(
    form_data: fastapi.security.OAuth2PasswordRequestForm = fastapi.Depends(),
):
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise fastapi.HTTPException(
            status_code=starlette.status.HTTP_400_BAD_REQUEST,
            detail="Incorrect username or password",
        )
    access_token_expires = datetime.timedelta(
        minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES
    )
    access_token = create_access_token(
        data={
            "sub": user.username,
            "scopes": settings.OAUTH2_SCHEME_SCOPES,  # form_data.scopes
        },
        expires_delta=access_token_expires,
    )
    # return {"access_token": access_token, "token_type": "bearer"}
    response = starlette.responses.Response(
        {"access_token": access_token, "token_type": "bearer"},
        status_code=starlette.status.HTTP_200_OK,
    )
    response.set_cookie(
        key="Authorization",
        value="Bearer {}".format(fastapi.encoders.jsonable_encoder(access_token)),
        httponly=True,
        max_age=60 * 60,
        expires=60 * 60,
        domain="glass.ip-spotlight.xxx.xxx",
    )
    return response

Is it possible to set a cookie while returning {"access_token": access_token, "token_type": "bearer"} so that I can test it with OpenAPI client ? The above fails with:

 File "/usr/local/lib/python3.6/site-packages/starlette/responses.py", line 42, in __init__
    self.body = self.render(content)
  File "/usr/local/lib/python3.6/site-packages/starlette/responses.py", line 54, in render
    return content.encode(self.charset)
AttributeError: 'dict' object has no attribute 'encode'

Do you know perhaps why is so?

On top of that, when I return sth like:

response = starlette.responses.RedirectResponse(
        url=app.url_path_for("looking_glass_applications")
    )
    response.set_cookie(
        key="Authorization",
        value="Bearer {}".format(fastapi.encoders.jsonable_encoder(access_token)),
        httponly=True,
        max_age=60 * 60,
        expires=60 * 60,
        domain="glass.ip-spotlight.aorta.net",
    )
    return response

and then visit:

@app.get("/apps/test")
async def lookup_bgp_network_prefix(
    current_user: User = fastapi.Security(get_current_active_user, scopes=["bgp"])
):
    return starlette.responses.JSONResponse(
        status_code=starlette.status.HTTP_200_OK, content={"resp": "xxx"}
    )

I get an error saying:

    response = await func(request)
  File "/usr/local/lib/python3.6/site-packages/fastapi/routing.py", line 119, in app
    dependency_overrides_provider=dependency_overrides_provider,
  File "/usr/local/lib/python3.6/site-packages/fastapi/dependencies/utils.py", line 490, in solve_dependencies
    dependency_cache=dependency_cache,
  File "/usr/local/lib/python3.6/site-packages/fastapi/dependencies/utils.py", line 490, in solve_dependencies
    dependency_cache=dependency_cache,
  File "/usr/local/lib/python3.6/site-packages/fastapi/dependencies/utils.py", line 507, in solve_dependencies
    if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
TypeError: unhashable type: 'OAuth2PasswordBearerWithCookie'

Do you know what exactly triggers the above error ?

Could you please advise how to make OAuth2 to work based on a cookie (for example) ?

Additional context

I am using the class described here https://github.com/tiangolo/fastapi/issues/480 is an extension of the OAuth2PasswordBearer class which now includes checking for an Authorization header and an Authorization cookie. This will use a header as preference and falls back to a cookie. Furthermore, you have to create the object of OAuth2PasswordBearerWithCookie and use this instead of the OAuth2PasswordBearer class (https://github.com/tiangolo/fastapi/blob/master/fastapi/security/oauth2.py).

class OAuth2PasswordBearerWithCookie(fastapi.openapi.models.OAuth2):
    def __init__(
        self,
        tokenUrl: str,
        scheme_name: str = None,
        scopes: dict = None,
        auto_error: bool = True,
    ):
        if not scopes:
            scopes = {}
        flows = fastapi.openapi.models.OAuthFlows(
            password={"tokenUrl": tokenUrl, "scopes": scopes}
        )
        super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)

    async def __call__(
        self, request: starlette.requests.Request
    ) -> typing.Optional[str]:
        header_authorization: str = request.headers.get("Authorization")
        cookie_authorization: str = request.cookies.get("Authorization")
        header_scheme, header_param = get_authorization_scheme_param(
            header_authorization
        )
        cookie_scheme, cookie_param = get_authorization_scheme_param(
            cookie_authorization
        )
        if header_scheme.lower() == "bearer":
            authorization = True
            scheme = header_scheme
            param = header_param

        elif cookie_scheme.lower() == "bearer":
            authorization = True
            scheme = cookie_scheme
            param = cookie_param
        else:
            authorization = False

        if not authorization or scheme.lower() != "bearer":
            if self.auto_error:
                raise HTTPException(
                    status_code=starlette.status.HTTP_401_UNAUTHORIZED,
                    detail="Not authenticated",
                    headers={"WWW-Authenticate": "Bearer"},
                )
            else:
                return None
        return param


oauth2_scheme = OAuth2PasswordBearerWithCookie(  # fastapi.security.OAuth2PasswordBearer
    tokenUrl="/token",
    scopes={
        "me": "diagnostics about the current user",
        "bgp": "capabilities about bgp route lookup",
    },
)

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:13 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
polokkcommented, Jan 4, 2021

my code:

#from fastapi.openapi.models import OAuth2
from fastapi.security import OAuth2
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
from fastapi.security.utils import get_authorization_scheme_param
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
from typing import Any, Dict, List, Union

class OAuth2PasswordBearerWithCookie(OAuth2):
    def __init__(
        self,
        tokenUrl: str,
        scheme_name: Optional[str] = None,
        scopes: Optional[Dict[str, str]] = None,
        auto_error: bool = True,
    ):
        if not scopes:
            scopes = {}
        flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "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.cookies.get("access_token")
        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
        return param

1reaction
ViviLearns2Codecommented, Sep 29, 2020

@nskalis Not an expert, but I played around a bit and it seems like importing OAuth2 from fastapi.security instead of fastapi.openapi.models does not lead to the error messages you are seeing anymore.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to store Oauth2 Access Token in a Cookie correctly?
1 Answer 1 · typical web application: store the tokens in your backend (database...) · native mobile application: store the refresh token in...
Read more >
Is storing an OAuth token in cookies bad practice?
Whether you can store the access_token in cookies depends on following things: Is the access_token stored in cookie encrypted or not (it ...
Read more >
How to store Access Tokens: Localstorage, Cookies ... - Ironeko
This article goes through the do's - and *don't*s - of how to store Access Tokens, with easy, **beginner friendly examples**.
Read more >
LocalStorage vs Cookies: All You Need To Know About ...
So, how do I use cookies to persists my OAuth 2.0 tokens? · Option 1: Store your access token in localStorage : prone...
Read more >
A Primer on OAuth 2.0 for Client-Side Applications: Part 3
Split Access Token Cookie Pattern · The OAuth 2.0 authorization server and the API Server have to be running under the same root...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found