Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ApiKey Header documentation #142

Open
meandus opened this issue Apr 7, 2019 · 39 comments · May be fixed by #4818
Open

ApiKey Header documentation #142

meandus opened this issue Apr 7, 2019 · 39 comments · May be fixed by #4818
Labels
confirmed docs Documentation about how to use FastAPI good first issue Good for newcomers reviewed

Comments

@meandus
Copy link

meandus commented Apr 7, 2019

Hi,

Do you have some documentation or example regarding configuration for ApiKey (header) and how to verify it on FastAPI ?

thanks in advance,

Rémy.

@wshayes
Copy link
Sponsor Contributor

wshayes commented Apr 7, 2019

Hi @meandus - @tiangolo has a full example that includes that functionality: https://github.com/tiangolo/full-stack-fastapi-postgresql/tree/cd112bd683dcb9017e5f84c0ed3e4974a52e5571/%7B%7Bcookiecutter.project_slug%7D%7D/backend/app/app

and plenty of tutorial info here: https://fastapi.tiangolo.com/tutorial/security/intro/

Also - the Gitter channel is already pretty active for these types of questions: https://gitter.im/tiangolo/fastapi

@tiangolo
Copy link
Owner

tiangolo commented Apr 9, 2019

Thanks @wshayes for your help here! Much appreciated as always 🎉

@meandus if you can use OAuth2, that tutorial and the project generator might help. If somehow you explicitly need something different than OAuth2, with some custom APIKeyHeader (as defined in OpenAPI), yes, it is supported, but it is not properly documented yet.

I suggest you check the security section in the docs shared by William, and after knowing how it works, if you need to explicitly use APIKeyHeader instead of OAuth2, you can from fastapi.security.api_key import APIKeyHeader.

At least while I update the docs with those specifics... 😁

@meandus
Copy link
Author

meandus commented Apr 9, 2019 via email

@tiangolo
Copy link
Owner

@meandus it depends mostly on your setup, how are you deploying, if you have CI, etc. These kinds of things normally go in environment variables. You can also put them in a file that you read. If the repo is public, then you don't commit that file, just use it during deployment.

If you use the project generators, those settings are read from environment variables, and are passed as environment variables by Docker, reading them from Docker config files.

@meandus
Copy link
Author

meandus commented Apr 13, 2019 via email

@tiangolo
Copy link
Owner

Great! Thanks for reporting back.

@bendog
Copy link

bendog commented Dec 9, 2019

It would be really great if someone could at least paste an example of how these headers might be used, i seem to be going through file after file of source code trying to track down anything that I could use as a reference to how to get an API KEY passed via a header to be used as authentication, and i'm not having any luck!
Thanks!

@wshayes
Copy link
Sponsor Contributor

wshayes commented Dec 9, 2019 via email

@bendog
Copy link

bendog commented Dec 9, 2019

I ended up working it out, here's how i solved the problem

security.py

from fastapi import Depends, HTTPException
from fastapi.security import APIKeyHeader
from starlette import status


X_API_KEY = APIKeyHeader(name='X-API-Key')


def check_authentication_header(x_api_key: str = Depends(X_API_KEY)):
    """ takes the X-API-Key header and converts it into the matching user object from the database """

    # this is where the SQL query for converting the API key into a user_id will go
    if x_api_key == "1234567890":
        # if passes validation check, return user data for API Key
        # future DB query will go here
        return {
            "id": 1234567890,
            "companies": [1, ],
            "sites": [],
        }
    # else raise 401
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Invalid API Key",
    )

main.py

...
from fastapi import APIRouter, Depends
from models import Result, User
from security import check_authentication_header
...
@app.get("/result/", response_model=List[Result])
def result(user: User = Depends(check_authentication_header)):
    """ return a list of test results """
    print('user', user)
    ...

@wshayes
Copy link
Sponsor Contributor

wshayes commented Dec 9, 2019

Looks like the beginnings of another great blog article :)

@tiangolo tiangolo added docs Documentation about how to use FastAPI good first issue Good for newcomers labels Feb 9, 2020
@bendog
Copy link

bendog commented Feb 24, 2020

@tiangolo any ideas on where this documentation should go?
I'll try and work on it this week

@moreinhardt
Copy link

moreinhardt commented Feb 26, 2020

@bendog Thanks for the code snippet! Would be great if you could add it to the documentation with a bit more explanation :-)

I want to add that you obviously need to create a User model first (it confused me that you return a dict but annotate it as a User in main.py).

@bendog
Copy link

bendog commented Feb 27, 2020

@moreinhardt Sounds good, which file should I use in the repo to create my draft?

@gmelodie
Copy link

gmelodie commented Jul 3, 2020

I don't see any references to this anywhere else in the code. Is this still relevant @tiangolo ? If so I could give it a go :)

@st3fan
Copy link

st3fan commented Aug 7, 2020

@bendog I don't really understand the difference between Security(X_API_KEY) vs Depends(X_API_KEY).

@bendog
Copy link

bendog commented Aug 31, 2020

@st3fan can you link me to the docs about Security(X_API_KEY)?
This might have been added since I solved my database driven API key issue

@raphaelauv
Copy link
Contributor

raphaelauv commented Sep 8, 2020

if api_key is not necessary in the endpoint you can go for

from fastapi import Security
from fastapi.security.api_key import APIKeyHeader

API_KEY = "1234567asdfgh"
API_KEY_NAME = "X-API-KEY"

api_key_header_auth = APIKeyHeader(name=API_KEY_NAME, auto_error=True)

async def get_api_key(api_key_header: str = Security(api_key_header_auth)):
    if api_key_header != API_KEY:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid API Key",
        )

@router.get('/health', dependencies=[Security(get_api_key)])
async def endpoint():

@mdaj06
Copy link

mdaj06 commented Oct 11, 2020

Is this available to take up?

@Paradoxis
Copy link

@bendog thanks for the example, but how would I apply this globally across the entire application? I'm playing around with middleware but I'm getting nowhere. This example would require me to add Depends(...) on all routes individually, which will eventually lead to someone messing up and forgetting it, leaving potentially sensitive routes exposed to the entire world

@Mause
Copy link
Contributor

Mause commented Nov 3, 2020

You could easily apply it to the whole application?

Here's an example from my own API:

    app.include_router(
        api,
        prefix='/api',
        dependencies=[Security(get_current_user, scopes=['openid'])],
    )

@mcat56
Copy link

mcat56 commented Apr 28, 2021

Global dependency with APIKeyHeader does not work

api_key_header = APIKeyHeader(name=API_KEY_NAME)

async def get_api_key(api_key_header: str = Security(api_key_header)):
    if api_key_header != API_KEY:
        raise HTTPException(
            status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials"
        )
    return api_key_header


app = FastAPI(dependencies=[Depends(get_api_key)])

https://fastapi.tiangolo.com/tutorial/dependencies/global-dependencies/

Even if I change it to use Header it still does not work.


async def get_api_key(api_key_header: str = Header(...)):
    if api_key_header != API_KEY:
        raise HTTPException(
            status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials"
        )
    return api_key_header


app = FastAPI(dependencies=[Depends(get_api_key)])

@AdrianJohnston
Copy link

Global dependency with APIKeyHeader does not work

api_key_header = APIKeyHeader(name=API_KEY_NAME)

async def get_api_key(api_key_header: str = Security(api_key_header)):
    if api_key_header != API_KEY:
        raise HTTPException(
            status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials"
        )
    return api_key_header


app = FastAPI(dependencies=[Depends(get_api_key)])

https://fastapi.tiangolo.com/tutorial/dependencies/global-dependencies/

Even if I change it to use Header it still does not work.


async def get_api_key(api_key_header: str = Header(...)):
    if api_key_header != API_KEY:
        raise HTTPException(
            status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials"
        )
    return api_key_header


app = FastAPI(dependencies=[Depends(get_api_key)])

Not sure if this will solve your problem, or whether you figured it out, but for anyone else experiencing this issue:
Nginx was stripping any headers with underscores.
I changed my API_KEY_NAME="access_token" to "API_KEY_NAME="access-token"" and it fixed my issue.

Alternatively, setting underscores_in_headers on; in your nginx.conf file should also work (I did not test this approach).

@Hi-tech9
Copy link

Hi-tech9 commented Jun 4, 2021

Does anyone know who has the answers to Netcade python essentials part 2 for modules test/quizzes one to four

@andydavidson
Copy link

Global dependency with APIKeyHeader does not work

It also wasn't working for me, I had 0.61.0 installed. I upgraded to the latest, 0.67.0, and now it is working for me.

Some choice lines from my application which might help someone piece together a working implementation of APIKeyHeader in 0.67.0 and later:

from fastapi import Depends, FastAPI, HTTPException, Header, Security
from fastapi.security.api_key import APIKeyHeader

API_KEY = "secure"
api_key_header_auth = APIKeyHeader(name="Api-key", auto_error=True)

def get_api_key(api_key_header: str = Security(api_key_header_auth)):
    if api_key_header != API_KEY:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid API Key",
        )

app = FastAPI(dependencies=[Depends(get_api_key)])

app.include_router(thing.one, tags=['One'], prefix="/one")
app.include_router(thing.two, tags=['Two'], prefix="/two")

@AlyanQ
Copy link

AlyanQ commented Dec 13, 2021

Global dependency with APIKeyHeader does not work

api_key_header = APIKeyHeader(name=API_KEY_NAME)

async def get_api_key(api_key_header: str = Security(api_key_header)):
    if api_key_header != API_KEY:
        raise HTTPException(
            status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials"
        )
    return api_key_header


app = FastAPI(dependencies=[Depends(get_api_key)])

https://fastapi.tiangolo.com/tutorial/dependencies/global-dependencies/
Even if I change it to use Header it still does not work.


async def get_api_key(api_key_header: str = Header(...)):
    if api_key_header != API_KEY:
        raise HTTPException(
            status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials"
        )
    return api_key_header


app = FastAPI(dependencies=[Depends(get_api_key)])

Not sure if this will solve your problem, or whether you figured it out, but for anyone else experiencing this issue: Nginx was stripping any headers with underscores. I changed my API_KEY_NAME="access_token" to "API_KEY_NAME="access-token"" and it fixed my issue.

Alternatively, setting underscores_in_headers on; in your nginx.conf file should also work (I did not test this approach).

I was beating myself for the past 3 hours trying figure out why my header keys weren't doing anything. BLESS YOU STRANGER.

@aimfeld
Copy link

aimfeld commented Jan 5, 2022

Is there a secure way to whitelist a certain domain which can use the API without providing an API key? We've struggled with this problem quite a bit, seems surprisingly difficult: https://stackoverflow.com/questions/70579689/whitelist-web-application-for-api-access-without-api-key

@septatrix
Copy link

I just found out that this even exists after examining how exactly the OAuth2 module works as I wanted to implement exactly this. While I am happy that this functionality is provided natively it is a big bummer that one has to study the code to stumble upon it. This is again a problem of the nonexistent in-depth API reference (#804) :/

@OverkillGuy OverkillGuy linked a pull request Apr 24, 2022 that will close this issue
@OverkillGuy
Copy link

Bump because I don't know what to do:

I've linked PR #4818 that should fix this issue, but it's been stagnant for a month with no review or interaction.

I'm hopeful that people in this thread can indeed find my new docs page via the PR, but I'd like even better to close the current thread with docs on fastAPI's own website!

Any idea how to push this work forward?

@yashdev9274
Copy link

Hey everyone, can I help in any way.

@angely-dev
Copy link

angely-dev commented Jan 12, 2023

Also need this. Thanks for the snippets here. What is the difference between Depends and Security?

def check_authentication_header(x_api_key: str = Depends(X_API_KEY)):
def check_authentication_header(x_api_key: str = Security(X_API_KEY)):

app = FastAPI(dependencies=[Depends(check_authentication_header)])
app = FastAPI(dependencies=[Security(check_authentication_header)])

Edit: as per the source code (params.py#L358-L380), Security is a subclass of Depends and only adds the scopes attribute related to OAuth2 scopes. Therefore, no need for this class for the API key security scheme.

Edit: like others, here is my two cents on how to do it with an example.

@milieere
Copy link

Hi,

I would like to add my solution (thanks to all the inspiration in this thread!) and illustrate how it works with Swagger UI, to have the complete picture. I don't need any additional logic to check the API key against the DB, since I am using the AWS API Gateway that does this for me. I only needed to pass the argument to the endpoint, so that users/developers can try API in the Swagger UI and supply their API key there.

In the case of the AWS API gateway, the header passed is 'x-api-key'.

All my endpoints with prefix /itadmin require authentication via API key. I, therefore, included dependency in the router:

from fastapi import APIRouter, Security
from fastapi.security import APIKeyHeader

from app.api.api_v1.endpoints import itadmin

router = APIRouter()

api_key = APIKeyHeader(name="x-api-key")

router.include_router(
    itadmin.router, 
    prefix="/itadmin", 
    tags=["itadmin"],  
    dependencies=[Security(api_key)]
)

If you then navigate to /docs to see your endpoints in the Swagger UI, you can notice that a new button 'Authorize' appeared. There, you can fill your API key and successfully test your endpoints. You can see that upon execution, the x-api-key header was added to the curl command preview:

curl -X 'GET' \
  'http://localhost:8000/api/v1/itadmin/' \
  -H 'accept: application/json' \
  -H 'x-api-key: XXXXXXXXXXXXYYYYYYYYYY111222'

Hope this can help someone to resolve her/his doubts regarding API key authentication via request headers in Swagger UI.

@WilliamStam
Copy link

so i wanted to have a token auth based on multiple options. ie header, query, cookie. couldnt find much documentation on it but with @bendog 's code this is how i got it working

from fastapi import HTTPException, Security
from fastapi.security import APIKeyHeader, APIKeyCookie, APIKeyQuery
from starlette import status
HEADER_API_KEY = APIKeyHeader(name='X-API-Key',auto_error=False)
COOKIE_API_KEY = APIKeyCookie(name='token', auto_error=False)
QUERY_API_KEY = APIKeyQuery(name='token', auto_error=False)


def check_authentication_header(
    header_api_key: str = Security(HEADER_API_KEY),
    cookie_api_key: str = Security(COOKIE_API_KEY),
    query_api_key: str = Security(QUERY_API_KEY),
):
    """ takes the X-API-Key header and converts it into the matching user object from the database """
    
    print(f"header: {header_api_key}")
    print(f"cookie: {cookie_api_key}")
    print(f"query: {query_api_key}")
   
    if "1234567890" in [header_api_key, cookie_api_key, query_api_key]:
        # if passes validation check, return user data for API Key
        # future DB query will go here
        return {
            "id": 1234567890,
            "companies": [1, ],
            "sites": [],
        }
    # else raise 401
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Invalid API Key",
    )

the trick here is to auto_error=False or else it will error on the first depends and not even try the others.

image

this can defs play nice with all kinds of auth stuff. like even allowing jwt tokens as well i suppose (untested).

@bjubert
Copy link

bjubert commented Oct 19, 2023

thx so much

@developer992
Copy link

developer992 commented Nov 28, 2023

what if you want your header name to be standard "Authorization: Bearer xxx"

how do i init this APIKeyHeader class then?

I tried passing in scheme param like so: APIKeyHeader(name="Authorization", scheme_name="Bearer"

but the token comes out with "Bearer xxx" and therefore, i cannot decode it
thanks!

@WilliamStam
Copy link

WilliamStam commented Nov 28, 2023

from fastapi.security import (
    HTTPAuthorizationCredentials,
    HTTPBearer
)
# change auto_error to False if this is your only auth method
BEARER = HTTPBearer(auto_error=False, description="Add the token to your bearer authentication") 

async def get_token(
    request:Request,
    bearer_key: HTTPAuthorizationCredentials = Security(BEARER)
):

    if bearer_key:
        bearer_key = bearer_key.credentials

    # token is now bearer_key
    return bearer_key

for multiple methods

QUERY_API_KEY = APIKeyQuery(name='token', auto_error=False, description="Pass the token via a query sting item ?token=xxx")
BEARER = HTTPBearer(auto_error=False, description="Add the token to your bearer authentication")
HEADER_API_KEY = APIKeyHeader(name='X-API-Key', auto_error=False, description="Add a header [X-API-Key] with the token")


async def get_token(
    request:Request,
    query_api_key: str = Security(QUERY_API_KEY),
    bearer_key: HTTPAuthorizationCredentials = Security(BEARER),
    header_api_key: str = Security(HEADER_API_KEY),
):
    if bearer_key:
        bearer_key = bearer_key.credentials
    
    
    token = next(
        (arg for arg in [query_api_key, bearer_key, header_api_key] if arg is not None),
        None
    )
    
    return token

@developer992
Copy link

It seems it's working with this, yes

BEARER = HTTPBearer(auto_error=False, description="Add the token to your bearer authentication")

Many thanks, @WilliamStam

@dengyunsheng250
Copy link

haven't it be closed ?

@Mahas1234
Copy link

Are there any more fixes needed. I'm looking for first contribution.

@sir-george2500
Copy link

is this still available for take up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed docs Documentation about how to use FastAPI good first issue Good for newcomers reviewed
Projects
None yet
Development

Successfully merging a pull request may close this issue.