Giter Site home page Giter Site logo

harrymwinters / fastapi-oidc Goto Github PK

View Code? Open in Web Editor NEW
64.0 4.0 16.0 90 KB

Verify and decrypt 3rd party OIDC ID tokens to protect your fastapi (https://github.com/tiangolo/fastapi) endpoints.

License: MIT License

Python 100.00%
fastapi oidc oidc-resource-server

fastapi-oidc's Introduction

FastAPI OIDC

Test Documentation Status Package version


โš ๏ธ See this issue for simple role-your-own example of checking OIDC tokens.

Verify and decrypt 3rd party OIDC ID tokens to protect your fastapi endpoints.

Documentation: ReadTheDocs

Source code: Github

Installation

pip install fastapi-oidc

Usage

Verify ID Tokens Issued by Third Party

This is great if you just want to use something like Okta or google to handle your auth. All you need to do is verify the token and then you can extract user ID info from it.

from fastapi import Depends
from fastapi import FastAPI

# Set up our OIDC
from fastapi_oidc import IDToken
from fastapi_oidc import get_auth

OIDC_config = {
    "client_id": "0oa1e3pv9opbyq2Gm4x7",
    # Audience can be omitted in which case the aud value defaults to client_id
    "audience": "https://yourapi.url.com/api",
    "base_authorization_server_uri": "https://dev-126594.okta.com",
    "issuer": "dev-126594.okta.com",
    "signature_cache_ttl": 3600,
}

authenticate_user: Callable = get_auth(**OIDC_config)

app = FastAPI()

@app.get("/protected")
def protected(id_token: IDToken = Depends(authenticate_user)):
    return {"Hello": "World", "user_email": id_token.email}

Using your own tokens

The IDToken class will accept any number of extra field but if you want to craft your own token class and validation that's accounted for too.

class CustomIDToken(fastapi_oidc.IDToken):
    custom_field: str
    custom_default: float = 3.14


authenticate_user: Callable = get_auth(**OIDC_config, token_type=CustomIDToken)

app = FastAPI()


@app.get("/protected")
def protected(id_token: CustomIDToken = Depends(authenticate_user)):
    return {"Hello": "World", "user_email": id_token.custom_default}

fastapi-oidc's People

Contributors

boneyaz avatar harrymwinters avatar samedii avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

fastapi-oidc's Issues

Some optional values are required

After running the simple flow with Google as authentication provider I get this error:

pydantic.error_wrappers.ValidationError: 6 validation errors for IDToken
name
  field required (type=value_error.missing)
preferred_username
  field required (type=value_error.missing)
auth_time
  field required (type=value_error.missing)
ver
  field required (type=value_error.missing)
amr
  field required (type=value_error.missing)
idp
  field required (type=value_error.missing)

Indeed these fields are not present in the JWT token that was generated by Google, but they are not listed as required in the spec. I believe they are always present in okta, but should be made optional to ensure compatibility with other providers.

Missing+parameter:+code_challenge_method.

Hello,

I have the next error: when I'm ussing grant_types=["authorization_code"],

Error:
Missing+parameter:+code_challenge_method.

How can I specify the code_challenge_method ?

thank you

Make IDToken extendable

Hi there,

thanks for sharing your project. We are currently implementing id_token validation in our project and found your library quite useful.

We would like to discuss a thought with you:
How about making the IDToken class configurable such that one may provide a Subclass with additional fields and validations?
Needless to say that IDToken currently allows for additional fields, but we believe that our idea might make some things easier.

Alternative to building a new repository/code base

Hi Harry,
thank you very much for the time you invested into the code of this repository!
I have had to walk the very stony path of figuring out how OpenID Connect and a resource server in FastAPI could work together, myself. And this helped quite a lot in understanding some details.

I also came up with a solution and was wondering what you were thinking about it. My approach was to use a maximum of the standard (existing) libraries to avoid any self-introduced security issues.
Also, I have tried to use the security models from FastAPI in order to automatically have them listed in the OpenAPI docs.

Basically, the two tools I am using are authlib (for verification of the access_token, which in my case is a Json Web Token (JWT)) and the FastAPI security model.

Here is how it should look once swagger-api/swagger-ui#3517 is fixed.
The code should already work (more or less - my final version is a bit more complex since I still try to work around the mentioned swagger-ui problem). Just a login via swagger-ui is not possible yet.

from fastapi.security.open_id_connect_url import OpenIdConnect
from starlette.middleware.sessions import SessionMiddleware
from authlib.integrations.starlette_client import OAuth
from fastapi import HTTPException
from fastapi import Request
from fastapi import Depends
from fastapi import status
import string
import random

# create your app first....
# then

# middleware required for authlib - though if you look into the code it is only used to obtain the "nonce"
# which is in this case always None and should not be checked anyways
# (since we are the resource server and the nonce is specified by the identity provider)
# but to reuse the code in authlib, we need to have the middleware installed
app.add_middleware(
    SessionMiddleware,
    secret_key="".join(random.choice(string.ascii_letters) for _ in range(16)),
)

oicd_discovery_url = "...." # the url before .well_known/...

authlib_oauth = OAuth()
authlib_oauth.register(
    name="myapp",
    server_metadata_url=oicd_discovery_url,
    #client_kwargs={"scope": "openid email offline_access profile"},
    #client_id="some-client-id", # if enabled, authlib will also check that the access token belongs to this client id (audience)
)

fastapi_oauth2 = OpenIdConnect(
    openIdConnectUrl=oicd_discovery_url,
    scheme_name="My Authentification Method",
)

async def current_user(
    request: Request, token: Optional[str] = Depends(fastapi_oauth2)
):
    # we could query the identity provider to give us some information about the user
    # userinfo = await self.authlib_oauth.myapp.userinfo(token={"access_token": token})

    # in my case, the JWT already contains all the information so I only need to decode and verify it
    try:
        # note that this also validates the JWT by validating all the claims
        user = await authlib_oauth.myapp.parse_id_token(
            request, token={"id_token": token}
        )
    except Exception as exp:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=f"Supplied authentication could not be validated ({exp})",
        )
    return user

@app.get( "/me" )
def me( user = Depends(current_user)):
     return user

It seems to me that authlib also checks a few more claims than you do in your auth.py.
Comments highly appreciated. Maybe it also helps.

Best regards,
Carsten

import error in example

from fastapi_oidc import IDToken
cant find the IDToken.

I could use it as bellow
from fastapi_oidc.types import IDToken
Screenshot 2021-08-14 at 12 58 50 PM

Audience in token can be a list too

Multiple types allowed is painful

aud
REQUIRED. Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value. It MAY also contain identifiers for other audiences. In the general case, the aud value is an array of case sensitive strings. In the common special case when there is one audience, the aud value MAY be a single case sensitive string.
https://openid.net/specs/openid-connect-core-1_0.html

Generate token problem, documentation-less library!

Hi, @HarryMWinters

1.) How can i generate token using this library on one endpoint? And then verify it on another endpoint.
2.) Can you show me (with code) how i can authenticate a user one endpoint and not for another one using post request not swagger UI?
3.)There is so less documentation about this library

happy coding

OpenIdConnect is used incorrectly

This function does not take issuer as it's argument. It takes the full "well known uri".

oauth2_scheme = OpenIdConnect(openIdConnectUrl=issuer)

should be something like

oauth2_scheme = OpenIdConnect(openIdConnectUrl="http://localhost:8080/auth/realms/my-realm/.well-known/openid-configuration")

If I input this as the "issuer" to the config then everything is discovered and /docs with login works (but requests fail because the "issuer" doesn't match obviously)

I'll see if I can fork and fix this stuff

Add CI

What good are test if they don't run? Also we should add some coverage.

Error while trying to use authenticate using mock oidc server

I am trying to add OIDC by following your example in the documentation. I am using a docker container for mock-oidc server with the following env variables:

      - PORT=9090
      - CLIENT_ID=my-client
      - CLIENT_SECRET=my-secret
      - CLIENT_REDIRECT_URI=http://localhost:8000/protected
      - CLIENT_LOGOUT_REDIRECT_URI=http://localhost:8000/logout

in main.py I have this -

OIDC_config = {
    "client_id": os.getenv("CLIENT_ID"), 
    "base_authorization_server_uri": "http://localhost:9090/.well-known/openid-configuration",
    "issuer": "http://localhost:9090/.well-known/openid-configuration",
    "signature_cache_ttl": 3600,
}

authenticate_user: Callable = get_auth(**OIDC_config)


@app.get("/protected")
def protected(id_token: IDToken = Depends(authenticate_user)):
    return {"Hello": "World", "user_email": id_token}

On clicking authorize, its throwing me redirect uri not matching. What could be the issue. Also, in the scope OpenIdConnect (OAuth2, refresh_token), its throwing client id is invalid. I am not sure why this is happening.
image
image

Could I somehow not land in the list of api's page in the first place. Basically I would want user to first get authenticated and then the list of api's would be shown. Thanks in advance :)

How to implement fastapi-oidc in my app?

Can you please provide a complete walk-through how to implement the example in an existing code?

I received the client_id, base_authorization_server_uri, issuer (must be the same as base_authorization_server_uri in my case, this is what I was told ), and I have tried using the code as shown in the readme with these values, but all I get is an empty "Available authorizations" dialog and "Not authenticated" error message in the Swagger UI.

So, what else should be added to a code in main.py like

import datetime, os, random, time
from pathlib import Path
from typing import Union, Optional
from fastapi import FastAPI, Depends, File, UploadFile, status
from fastapi.responses import FileResponse

from collections.abc import Callable
from fastapi_oidc import IDToken, get_auth

OIDC_config = {
    "client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "base_authorization_server_uri": "https://url",
    "issuer": "https://url",
    "signature_cache_ttl": 3600,
}
swagger_ui_init_oauth = {
    "clientId": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "appName": "name",
    "usePkceWithAuthorizationCodeGrant": "true",
    ###"clientSecret": "secret",   ### Told not to use it if I use PKCE
    "oauth2RedirectUrl": "https://redirURL",
    "scopeSeparator": " ",
    "scopes": "scopes...",
}

authenticate_user: Callable = get_auth(**OIDC_config)

app = FastAPI(swagger_ui_init_oauth=swagger_ui_init_oauth)

@app.post("/highlight_file/", tags=['highlight_file'])
async def highlight_file(file: UploadFile = File(...), id_token: IDToken = Depends(authenticate_user)):
    print(f'ID Token: {id_token}')
    return await highlight_file_function(file)

async def highlight_file_function(file):
    # process file code

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.