Skip to content

Commit

Permalink
Add extraction for access token (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
tokusumi authored Feb 25, 2021
1 parent a8db880 commit 93b92f6
Show file tree
Hide file tree
Showing 24 changed files with 893 additions and 253 deletions.
5 changes: 5 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[flake8]
max-line-length = 88
select = C,E,F,W,B,B9
ignore = E203, E501, W503
exclude = __init__.py
68 changes: 59 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ fastapi-cloudauth standardizes and simplifies the integration between FastAPI an
## Features

* [X] Verify access/id token
* [X] Authenticate permission based on scope (or groups) within access token
* [X] Get login user info (name, email, etc.) within ID token
* [X] Authenticate permission based on scope (or groups) within access token and Extract user info
* [X] Get the detail of login user info (name, email, etc.) within ID token
* [X] Dependency injection for verification/getting user, powered by [FastAPI](https://github.com/tiangolo/fastapi)
* [X] Support for:
* [X] [AWS Cognito](https://aws.amazon.com/jp/cognito/)
Expand All @@ -35,14 +35,15 @@ $ pip install fastapi-cloudauth
* Create a user assigned `read:users` permission in AWS Cognito
* Get Access/ID token for the created user

NOTE: access token is valid for verification and scope-based authentication. ID token is valid for verification and getting user info from claims.
NOTE: access token is valid for verification, scope-based authentication and getting user info (optional). ID token is valid for verification and getting full user info from claims.

### Create it

Create a file main.py with:

```python3
import os
from pydantic import BaseModel
from fastapi import FastAPI, Depends
from fastapi_cloudauth.cognito import Cognito, CognitoCurrentUser, CognitoClaims

Expand All @@ -56,14 +57,24 @@ def secure():
return "Hello"


class AccessUser(BaseModel):
sub: str


@app.get("/access/")
def secure_access(current_user: AccessUser = Depends(auth.claim(AccessUser))):
# access token is valid and getting user info from access token
return f"Hello", {current_user.sub}


get_current_user = CognitoCurrentUser(
region=os.environ["REGION"], userPoolId=os.environ["USERPOOLID"]
)


@app.get("/user/")
def secure_user(current_user: CognitoClaims = Depends(get_current_user)):
# ID token is valid
# ID token is valid and getting user info from ID token
return f"Hello, {current_user.username}"
```

Expand Down Expand Up @@ -105,6 +116,7 @@ Create a file main.py with:

```python3
import os
from pydantic import BaseModel
from fastapi import FastAPI, Depends
from fastapi_cloudauth.auth0 import Auth0, Auth0CurrentUser, Auth0Claims

Expand All @@ -119,12 +131,22 @@ def secure():
return "Hello"


class AccessUser(BaseModel):
sub: str


@app.get("/access/")
def secure_access(current_user: AccessUser = Depends(auth.claim(AccessUser))):
# access token is valid and getting user info from access token
return f"Hello", {current_user.sub}


get_current_user = Auth0CurrentUser(domain=os.environ["DOMAIN"])


@app.get("/user/")
def secure_user(current_user: Auth0Claims = Depends(get_current_user)):
# ID token is valid
# ID token is valid and getting user info from ID token
return f"Hello, {current_user.username}"
```

Expand Down Expand Up @@ -153,15 +175,18 @@ get_current_user = FirebaseCurrentUser()

@app.get("/user/")
def secure_user(current_user: FirebaseClaims = Depends(get_current_user)):
# ID token is valid
# ID token is valid and getting user info from ID token
return f"Hello, {current_user.user_id}"
```

Try to run the server and see interactive UI in the same way.

## Custom claims
## Additional User Information

We can get values for the current user from access/ID token by writing a few lines.

### Custom Claims

We can get values for the current user by writing a few lines.
For Auth0, the ID token contains extra values as follows (Ref at [Auth0 official doc](https://auth0.com/docs/tokens)):

```json
Expand All @@ -183,7 +208,7 @@ For Auth0, the ID token contains extra values as follows (Ref at [Auth0 official

By default, `Auth0CurrentUser` gives `pydantic.BaseModel` object, which has `username` (name) and `email` fields.

Here is sample code for extracting extra user information (adding `user_id`):
Here is sample code for extracting extra user information (adding `user_id`) from ID token:

```python3
from pydantic import Field
Expand All @@ -197,6 +222,31 @@ get_current_user = Auth0CurrentUser(domain=DOMAIN)
get_current_user.user_info = CustomAuth0Claims # override user info model with a custom one.
```

Or, we can also set new custom claims as follows:

```python3
get_user_detail = get_current_user.claim(CustomAuth0Claims)

@app.get("/new/")
async def detail(user: CustomAuth0Claims = Depends(get_user_detail)):
return f"Hello, {user.user_id}"
```

### Raw payload

If you doesn't require `pydantic` data serialization (validation), `FastAPI-CloudAuth` has a option to extract raw payload.

All you need is:

```python3
get_raw_info = get_current_user.claim(None)

@app.get("/new/")
async def raw_detail(user = Depends(get_raw_info)):
# user has all items (ex. iss, sub, aud, exp, ... it depends on passed token)
return f"Hello, {user.get('sub')}"
```

## Development - Contributing

Please read the [CONTRIBUTING](../CONTRIBUTING.md) how to setup development environment and testing.
44 changes: 44 additions & 0 deletions docs/server/auth0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import os
from pydantic import BaseModel
from fastapi import FastAPI, Depends
from fastapi_cloudauth.auth0 import Auth0, Auth0CurrentUser, Auth0Claims

tags_metadata = [
{
"name": "Auth0",
"description": "Operations with access/ID token, provided by Auth0.",
}
]

app = FastAPI(
title="FastAPI CloudAuth Project",
description="Simple integration between FastAPI and cloud authentication services (AWS Cognito, Auth0, Firebase Authentication).",
openapi_tags=tags_metadata,
)

auth = Auth0(domain=os.environ["AUTH0_DOMAIN"])


@app.get("/", dependencies=[Depends(auth.scope("read:users"))], tags=["Auth0"])
def secure():
# access token is valid
return "Hello"


class AccessUser(BaseModel):
sub: str


@app.get("/access/", tags=["Auth0"])
def secure_access(current_user: AccessUser = Depends(auth.claim(AccessUser))):
# access token is valid and getting user info from access token
return f"Hello", {current_user.sub}


get_current_user = Auth0CurrentUser(domain=os.environ["AUTH0_DOMAIN"])


@app.get("/user/", tags=["Auth0"])
def secure_user(current_user: Auth0Claims = Depends(get_current_user)):
# ID token is valid and getting user info from ID token
return f"Hello, {current_user.username}"
48 changes: 48 additions & 0 deletions docs/server/cognito.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import os
from pydantic import BaseModel
from fastapi import FastAPI, Depends
from fastapi_cloudauth.cognito import Cognito, CognitoCurrentUser, CognitoClaims

tags_metadata = [
{
"name": "Cognito",
"description": "Operations with access/ID token, provided by AWS Cognito.",
}
]

app = FastAPI(
title="FastAPI CloudAuth Project",
description="Simple integration between FastAPI and cloud authentication services (AWS Cognito, Auth0, Firebase Authentication).",
openapi_tags=tags_metadata,
)

auth = Cognito(
region=os.environ["COGNITO_REGION"], userPoolId=os.environ["COGNITO_USERPOOLID"]
)


@app.get("/", dependencies=[Depends(auth.scope("read:users"))], tags=["Cognito"])
def secure():
# access token is valid
return "Hello"


class AccessUser(BaseModel):
sub: str


@app.get("/access/", tags=["Cognito"])
def secure_access(current_user: AccessUser = Depends(auth.claim(AccessUser))):
# access token is valid and getting user info from access token
return f"Hello", {current_user.sub}


get_current_user = CognitoCurrentUser(
region=os.environ["COGNITO_REGION"], userPoolId=os.environ["COGNITO_USERPOOLID"]
)


@app.get("/user/", tags=["Cognito"])
def secure_user(current_user: CognitoClaims = Depends(get_current_user)):
# ID token is valid and getting user info from ID token
return f"Hello, {current_user.username}"
23 changes: 23 additions & 0 deletions docs/server/firebase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from fastapi import FastAPI, Depends
from fastapi_cloudauth.firebase import FirebaseCurrentUser, FirebaseClaims

tags_metadata = [
{
"name": "Firebase",
"description": "Operations with access/ID token, provided by Firebase Authentication.",
}
]

app = FastAPI(
title="FastAPI CloudAuth Project",
description="Simple integration between FastAPI and cloud authentication services (AWS Cognito, Auth0, Firebase Authentication).",
openapi_tags=tags_metadata,
)

get_current_user = FirebaseCurrentUser()


@app.get("/user/", tags=["Firebase"])
def secure_user(current_user: FirebaseClaims = Depends(get_current_user)):
# ID token is valid and getting user info from ID token
return f"Hello, {current_user.user_id}"
29 changes: 21 additions & 8 deletions fastapi_cloudauth/auth0.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,46 @@
from typing import Any, Optional

from pydantic import BaseModel, Field
from .base import TokenVerifier, TokenUserInfoGetter, JWKS

from .base import ScopedAuth, UserInfoAuth
from .verification import JWKS


class Auth0(TokenVerifier):
class Auth0(ScopedAuth):
"""
Verify access token of auth0
"""

scope_key = "permissions"
user_info = None

def __init__(self, domain: str, *args, **kwargs):
def __init__(
self,
domain: str,
scope_key: Optional[str] = "permissions",
auto_error: bool = True,
):
url = f"https://{domain}/.well-known/jwks.json"
jwks = JWKS.fromurl(url)
super().__init__(jwks, *args, **kwargs)
super().__init__(
jwks, scope_key=scope_key, auto_error=auto_error,
)


class Auth0Claims(BaseModel):
username: str = Field(alias="name")
email: str = Field(None, alias="email")


class Auth0CurrentUser(TokenUserInfoGetter):
class Auth0CurrentUser(UserInfoAuth):
"""
Verify ID token and get user info of Auth0
"""

user_info = Auth0Claims

def __init__(self, domain: str, *args, **kwargs):
def __init__(
self, domain: str, *args: Any, **kwargs: Any,
):
url = f"https://{domain}/.well-known/jwks.json"
jwks = JWKS.fromurl(url)
super().__init__(jwks, *args, **kwargs)
super().__init__(jwks, *args, user_info=self.user_info, **kwargs)
Loading

0 comments on commit 93b92f6

Please sign in to comment.