Skip to content

Commit 44c46c1

Browse files
Merge pull request #9 from paulussimanjuntak/docs
add docs installation & usage
2 parents ac05150 + b6f841a commit 44c46c1

File tree

8 files changed

+361
-12
lines changed

8 files changed

+361
-12
lines changed

docs/index.md

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
# Welcome to MkDocs
1+
<h1 align="center" style="margin-bottom: 20px; font-weight: 500; font-size: 50px;">
2+
FastAPI JWT Auth
3+
</h1>
4+
<p align="center">
5+
<em>FastAPI extension that provides JWT Auth support (secure, easy to use, and lightweight)</em>
6+
</p>
27

3-
For full documentation visit [mkdocs.org](https://www.mkdocs.org).
8+
[![Build Status](https://travis-ci.org/IndominusByte/fastapi-jwt-auth.svg?branch=master)](https://travis-ci.org/IndominusByte/fastapi-jwt-auth)
9+
[![Coverage Status](https://coveralls.io/repos/github/IndominusByte/fastapi-jwt-auth/badge.svg?branch=master)](https://coveralls.io/github/IndominusByte/fastapi-jwt-auth?branch=master)
10+
[![PyPI version](https://badge.fury.io/py/fastapi-jwt-auth.svg)](https://badge.fury.io/py/fastapi-jwt-auth)
11+
[![Downloads](https://pepy.tech/badge/fastapi-jwt-auth)](https://pepy.tech/project/fastapi-jwt-auth)
412

5-
## Commands
13+
---
614

7-
* `mkdocs new [dir-name]` - Create a new project.
8-
* `mkdocs serve` - Start the live-reloading docs server.
9-
* `mkdocs build` - Build the documentation site.
10-
* `mkdocs -h` - Print help message and exit.
15+
**Documentation**: <a href="https://IndominusByte.github.io/fastapi-jwt-auth/" target="_blank">https://IndominusByte.github.io/fastapi-jwt-auth/</a>
1116

12-
## Project layout
17+
**Source Code**: <a href="https://github.com/IndominusByte/fastapi-jwt-auth/" target="_blank">https://github.com/IndominusByte/fastapi-jwt-auth/</a>
1318

14-
mkdocs.yml # The configuration file.
15-
docs/
16-
index.md # The documentation homepage.
17-
... # Other markdown pages, images and other files.
19+
---
20+
21+
## Features
22+
FastAPI extension that provides JWT Auth support (secure, easy to use and lightweight), if you were familiar with flask-jwt-extended this extension suitable for you because this extension inspired by flask-jwt-extended.
23+
24+
- Access token and refresh token
25+
- Token freshness will only allow fresh tokens to access endpoint
26+
- Token revoking/blacklisting
27+
- Custom token revoking

docs/installation.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
The easiest way to start working with this extension with pip
2+
3+
```bash
4+
pip install fastapi-jwt-auth
5+
```
6+
7+
If you want to use asymmetric (public/private) key signing algorithms, include the <b>asymmetric</b> extra requirements.
8+
```bash
9+
pip install 'fastapi-jwt-auth[asymmetric]'
10+
```

docs/usage/basic.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
Create a file `basic.py`:
2+
3+
```python
4+
from fastapi import FastAPI, HTTPException, Depends, Request
5+
from fastapi.responses import JSONResponse
6+
from fastapi_jwt_auth import AuthJWT
7+
from fastapi_jwt_auth.exceptions import AuthJWTException
8+
from pydantic import BaseModel
9+
10+
app = FastAPI()
11+
12+
class User(BaseModel):
13+
username: str
14+
password: str
15+
16+
# in production you can use Settings management
17+
# from pydantic to get secret key from .env
18+
class Settings(BaseModel):
19+
authjwt_secret_key: str = "secret"
20+
21+
# callback to get your configuration
22+
@AuthJWT.load_config
23+
def get_config():
24+
return Settings()
25+
26+
# exception handler for authjwt
27+
# in production, you can tweak performance using orjson response
28+
@app.exception_handler(AuthJWTException)
29+
def authjwt_exception_handler(request: Request, exc: AuthJWTException):
30+
return JSONResponse(
31+
status_code=exc.status_code,
32+
content={"detail": exc.message}
33+
)
34+
35+
# provide a method to create access tokens. The create_access_token()
36+
# function is used to actually generate the token to use authorization
37+
# later in endpoint protected
38+
@app.post('/login')
39+
def login(user: User, Authorize: AuthJWT = Depends()):
40+
if user.username != "test" and user.password != "test":
41+
raise HTTPException(status_code=401,detail="Bad username or password")
42+
43+
# subject identifier for who this token is for example id or username from database
44+
access_token = Authorize.create_access_token(subject=user.username)
45+
return {"access_token": access_token}
46+
47+
# protect endpoint with function jwt_required(), which requires
48+
# a valid access token in the request headers to access.
49+
@app.get('/user')
50+
def user(Authorize: AuthJWT = Depends()):
51+
Authorize.jwt_required()
52+
53+
current_user = Authorize.get_jwt_subject()
54+
return {"user": current_user}
55+
```
56+
57+
Run the server with:
58+
59+
```bash
60+
$ uvicorn basic:app --host 0.0.0.0
61+
62+
INFO: Started server process [9859]
63+
INFO: Waiting for application startup.
64+
INFO: Application startup complete.
65+
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
66+
```
67+
68+
To access a jwt_required protected url, all we have to do is send in the JWT with the request. By default, this is done with an authorization header that looks like:
69+
70+
```
71+
Authorization: Bearer <access_token>
72+
```
73+
74+
We can see this in action using <b>curl</b>:
75+
76+
```bash
77+
$ curl http://localhost:8000/user
78+
79+
{"detail":"Missing Authorization Header"}
80+
81+
$ curl -H "Content-Type: application/json" -X POST \
82+
-d '{"username":"test","password":"test"}' http://localhost:8000/login
83+
84+
{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjAzNjkyMjYxLCJuYmYiOjE2MDM2OTIyNjEsImp0aSI6IjZiMjZkZTkwLThhMDYtNDEzMy04MzZiLWI5ODJkZmI3ZjNmZSIsImV4cCI6MTYwMzY5MzE2MSwidHlwZSI6ImFjY2VzcyIsImZyZXNoIjpmYWxzZX0.ro5JMHEVuGOq2YsENkZigSpqMf5cmmgPP8odZfxrzJA"}
85+
86+
$ export TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjAzNjkyMjYxLCJuYmYiOjE2MDM2OTIyNjEsImp0aSI6IjZiMjZkZTkwLThhMDYtNDEzMy04MzZiLWI5ODJkZmI3ZjNmZSIsImV4cCI6MTYwMzY5MzE2MSwidHlwZSI6ImFjY2VzcyIsImZyZXNoIjpmYWxzZX0.ro5JMHEVuGOq2YsENkZigSpqMf5cmmgPP8odZfxrzJA
87+
88+
$ curl -H "Authorization: Bearer $TOKEN" http://localhost:8000
89+
90+
{"user":"test"}
91+
```

docs/usage/freshness.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
The fresh token pattern is built into this extension. this pattern is very simple, you can choose to mark some access tokens as fresh and other as a non-fresh token, and use the <b>fresh_jwt_required()</b> function to only allow fresh tokens to access the certain endpoint.
2+
3+
This is useful for allowing the fresh token to do some critical things (such as update information user) in real case you can see in the GitHub system when user wants to delete a repository in a certain time you need login if token not fresh again. Utilizing Fresh tokens in conjunction with refresh tokens can lead to a more secure site, without creating a bad user experience by making users constantly re-authenticate.
4+
5+
Here is an example of how you could utilize refresh tokens with the fresh token pattern:
6+
7+
```python
8+
from fastapi import FastAPI, HTTPException, Depends, Request
9+
from fastapi.responses import JSONResponse
10+
from fastapi_jwt_auth import AuthJWT
11+
from fastapi_jwt_auth.exceptions import AuthJWTException
12+
from pydantic import BaseModel
13+
14+
app = FastAPI()
15+
16+
class User(BaseModel):
17+
username: str
18+
password: str
19+
20+
class Settings(BaseModel):
21+
authjwt_secret_key: str = "secret"
22+
23+
@AuthJWT.load_config
24+
def get_config():
25+
return Settings()
26+
27+
@app.exception_handler(AuthJWTException)
28+
def authjwt_exception_handler(request: Request, exc: AuthJWTException):
29+
return JSONResponse(
30+
status_code=exc.status_code,
31+
content={"detail": exc.message}
32+
)
33+
34+
# Standard login endpoint. Will return a fresh access token and a refresh token
35+
@app.post('/login')
36+
def login(user: User, Authorize: AuthJWT = Depends()):
37+
if user.username != "test" and user.password != "test":
38+
raise HTTPException(status_code=401,detail="Bad username or password")
39+
40+
"""
41+
create_access_token supports an optional 'fresh' argument,
42+
which marks the token as fresh or non-fresh accordingly.
43+
As we just verified their username and password, we are
44+
going to mark the token as fresh here.
45+
"""
46+
access_token = Authorize.create_access_token(subject=user.username,fresh=True)
47+
refresh_token = Authorize.create_refresh_token(subject=user.username)
48+
return {"access_token": access_token, "refresh_token": refresh_token}
49+
50+
@app.post('/refresh')
51+
def refresh(Authorize: AuthJWT = Depends()):
52+
"""
53+
Refresh token endpoint. This will generate a new access token from
54+
the refresh token, but will mark that access token as non-fresh,
55+
as we do not actually verify a password in this endpoint.
56+
"""
57+
Authorize.jwt_refresh_token_required()
58+
59+
current_user = Authorize.get_jwt_subject()
60+
new_access_token = Authorize.create_access_token(subject=current_user,fresh=False)
61+
return {"access_token": new_access_token}
62+
63+
@app.post('/fresh-login')
64+
def fresh_login(user: User, Authorize: AuthJWT = Depends()):
65+
"""
66+
Fresh login endpoint. This is designed to be used if we need to
67+
make a fresh token for a user (by verifying they have the
68+
correct username and password). Unlike the standard login endpoint,
69+
this will only return a new access token, so that we don't keep
70+
generating new refresh tokens, which entirely defeats their point.
71+
"""
72+
if user.username != "test" and user.password != "test":
73+
raise HTTPException(status_code=401,detail="Bad username or password")
74+
75+
new_access_token = Authorize.create_access_token(subject=user.username,fresh=True)
76+
return {"access_token": new_access_token}
77+
78+
# Any valid JWT access token can access this endpoint
79+
@app.get('/protected')
80+
def protected(Authorize: AuthJWT = Depends()):
81+
Authorize.jwt_required()
82+
83+
current_user = Authorize.get_jwt_subject()
84+
return {"user": current_user}
85+
86+
# Only fresh JWT access token can access this endpoint
87+
@app.get('/protected-fresh')
88+
def protected_fresh(Authorize: AuthJWT = Depends()):
89+
Authorize.fresh_jwt_required()
90+
91+
current_user = Authorize.get_jwt_subject()
92+
return {"user": current_user}
93+
```

docs/usage/optional.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
In some cases you want to use one endpoint for both, protected and unprotected. in this situation you can use function <b>jwt_optional()</b>. this will allow the endpoint to be accessed regardless of if a JWT is sent in the request or not. if a JWT get tampering or expired an error will be returned instead of calling the endpoint.
2+
3+
```python
4+
from fastapi import FastAPI, HTTPException, Depends, Request
5+
from fastapi.responses import JSONResponse
6+
from fastapi_jwt_auth import AuthJWT
7+
from fastapi_jwt_auth.exceptions import AuthJWTException
8+
from pydantic import BaseModel
9+
10+
app = FastAPI()
11+
12+
class User(BaseModel):
13+
username: str
14+
password: str
15+
16+
class Settings(BaseModel):
17+
authjwt_secret_key: str = "secret"
18+
19+
@AuthJWT.load_config
20+
def get_config():
21+
return Settings()
22+
23+
@app.exception_handler(AuthJWTException)
24+
def authjwt_exception_handler(request: Request, exc: AuthJWTException):
25+
return JSONResponse(
26+
status_code=exc.status_code,
27+
content={"detail": exc.message}
28+
)
29+
30+
@app.post('/login')
31+
def login(user: User, Authorize: AuthJWT = Depends()):
32+
if user.username != "test" and user.password != "test":
33+
raise HTTPException(status_code=401,detail="Bad username or password")
34+
35+
access_token = Authorize.create_access_token(subject=user.username)
36+
return {"access_token": access_token}
37+
38+
@app.get('/partially-protected')
39+
def partially_protected(Authorize: AuthJWT = Depends()):
40+
Authorize.jwt_optional()
41+
42+
# If no jwt is sent in the request, get_jwt_subject() will return None
43+
current_user = Authorize.get_jwt_subject() or "anonymous"
44+
return {"user": current_user}
45+
```

docs/usage/refresh.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
These are long-lived tokens which can be used to create new access tokens once an old access token has expired. refresh tokens cannot access an endpoint that is protected with <b>jwt_required()</b>, <b>jwt_optional()</b>, and <b>fresh_jwt_required()</b> and access tokens cannot access an endpoint that is protected with <b>jwt_refresh_token_required()</b>.
2+
3+
Utilizing refresh tokens we can help reduce the damage that can be done if an access token is stolen. however, if an attacker gets a refresh token they can keep generating new access tokens and accessing protected endpoints as though he was that user. we can help combat this by using the fresh token pattern, discussed in the next section.
4+
5+
Here is an example of using access and refresh tokens:
6+
7+
```python
8+
from fastapi import FastAPI, HTTPException, Depends, Request
9+
from fastapi.responses import JSONResponse
10+
from fastapi_jwt_auth import AuthJWT
11+
from fastapi_jwt_auth.exceptions import AuthJWTException
12+
from pydantic import BaseModel
13+
14+
app = FastAPI()
15+
16+
class User(BaseModel):
17+
username: str
18+
password: str
19+
20+
class Settings(BaseModel):
21+
authjwt_secret_key: str = "secret"
22+
23+
@AuthJWT.load_config
24+
def get_config():
25+
return Settings()
26+
27+
@app.exception_handler(AuthJWTException)
28+
def authjwt_exception_handler(request: Request, exc: AuthJWTException):
29+
return JSONResponse(
30+
status_code=exc.status_code,
31+
content={"detail": exc.message}
32+
)
33+
34+
@app.post('/login')
35+
def login(user: User, Authorize: AuthJWT = Depends()):
36+
if user.username != "test" and user.password != "test":
37+
raise HTTPException(status_code=401,detail="Bad username or password")
38+
39+
# Use create_access_token() and create_refresh_token() to create our
40+
# access and refresh tokens
41+
access_token = Authorize.create_access_token(subject=user.username)
42+
refresh_token = Authorize.create_refresh_token(subject=user.username)
43+
return {"access_token": access_token, "refresh_token": refresh_token}
44+
45+
@app.post('/refresh')
46+
def refresh(Authorize: AuthJWT = Depends()):
47+
"""
48+
The jwt_refresh_token_required() function insures a valid refresh
49+
token is present in the request before running any code below that function.
50+
we can use the get_jwt_subject() function to get the subject of the refresh
51+
token, and use the create_access_token() function again to make a new access token
52+
"""
53+
Authorize.jwt_refresh_token_required()
54+
55+
current_user = Authorize.get_jwt_subject()
56+
new_access_token = Authorize.create_access_token(subject=current_user)
57+
return {"access_token": new_access_token}
58+
59+
@app.get('/protected')
60+
def protected(Authorize: AuthJWT = Depends()):
61+
Authorize.jwt_required()
62+
63+
current_user = Authorize.get_jwt_subject()
64+
return {"user": current_user}
65+
```

docs/usage/revoking.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
This will allow you to revoke a specific token so that it can no longer access your endpoints.you will have to choose what token you want to check against the denylist. Denylist works by providing a callback function to this extension, using the <b>token_in_denylist_loader()</b>. This method will be called whenever the specified token <i>(access and/or refresh)</i> is used to access a protected endpoint. If the callback function says that the token is revoked, we will not allow the requester to continue, otherwise we will allow the requester to access the endpoint as normal.
2+
3+
Here is a basic example use token revoking:
4+
5+
```python
6+
```
7+
8+
In production, you will likely want to use either a database or in-memory store (such as Redis) to store your tokens. memory stores are great if you are wanting to revoke a token when the users log out and you can define timeout to your token in Redis, after the timeout has expired, the token will automatically be deleted.
9+
10+
Here example use Redis for revoking a token:
11+
12+
```python
13+
```

mkdocs.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,25 @@ theme:
55

66
repo_name: IndominusByte/fastapi-jwt-auth
77
repo_url: https://github.com/IndominusByte/fastapi-jwt-auth
8+
9+
markdown_extensions:
10+
- pymdownx.highlight:
11+
linenums_style: pymdownx.inline
12+
- pymdownx.superfences
13+
- pymdownx.inlinehilite
14+
15+
nav:
16+
- About: index.md
17+
- Installation: installation.md
18+
- Usage:
19+
- Basic Usage: usage/basic.md
20+
- Partially Protecting: usage/optional.md
21+
- Refresh Tokens: usage/refresh.md
22+
- Token Freshness: usage/freshness.md
23+
- Token Revoking: usage/revoking.md
24+
- Configuration Options:
25+
- General Options: configuration-options/general.md
26+
- Headers Options: configuration-options/headers.md
27+
- Cookies Options: configuration-options/cookies.md
28+
- CSRF Options: configuration-options/csrf.md
29+
- Denylist Options: configuration-options/denylist.md

0 commit comments

Comments
 (0)