Skip to content

Commit 6d5d69c

Browse files
committed
Add initial project
0 parents  commit 6d5d69c

File tree

3 files changed

+342
-0
lines changed

3 files changed

+342
-0
lines changed

Diff for: .gitignore

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
### Python template
2+
# Byte-compiled / optimized / DLL files
3+
__pycache__/
4+
*.py[cod]
5+
*$py.class
6+
7+
# C extensions
8+
*.so
9+
10+
# Distribution / packaging
11+
.Python
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
wheels/
24+
share/python-wheels/
25+
*.egg-info/
26+
.installed.cfg
27+
*.egg
28+
MANIFEST
29+
30+
# PyInstaller
31+
# Usually these files are written by a python script from a template
32+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
33+
*.manifest
34+
*.spec
35+
36+
# Installer logs
37+
pip-log.txt
38+
pip-delete-this-directory.txt
39+
40+
# Unit test / coverage reports
41+
htmlcov/
42+
.tox/
43+
.nox/
44+
.coverage
45+
.coverage.*
46+
.cache
47+
nosetests.xml
48+
coverage.xml
49+
*.cover
50+
*.py,cover
51+
.hypothesis/
52+
.pytest_cache/
53+
cover/
54+
55+
# Translations
56+
*.mo
57+
*.pot
58+
59+
# Django stuff:
60+
*.log
61+
local_settings.py
62+
db.sqlite3
63+
db.sqlite3-journal
64+
65+
# Flask stuff:
66+
instance/
67+
.webassets-cache
68+
69+
# Scrapy stuff:
70+
.scrapy
71+
72+
# Sphinx documentation
73+
docs/_build/
74+
75+
# PyBuilder
76+
.pybuilder/
77+
target/
78+
79+
# Jupyter Notebook
80+
.ipynb_checkpoints
81+
82+
# IPython
83+
profile_default/
84+
ipython_config.py
85+
86+
# pyenv
87+
# For a library or package, you might want to ignore these files since the code is
88+
# intended to run in multiple environments; otherwise, check them in:
89+
# .python-version
90+
91+
# pipenv
92+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
94+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
95+
# install all needed dependencies.
96+
#Pipfile.lock
97+
98+
# poetry
99+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
100+
# This is especially recommended for binary packages to ensure reproducibility, and is more
101+
# commonly ignored for libraries.
102+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
103+
#poetry.lock
104+
105+
# pdm
106+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
107+
#pdm.lock
108+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
109+
# in version control.
110+
# https://pdm.fming.dev/#use-with-ide
111+
.pdm.toml
112+
113+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
114+
__pypackages__/
115+
116+
# Celery stuff
117+
celerybeat-schedule
118+
celerybeat.pid
119+
120+
# SageMath parsed files
121+
*.sage.py
122+
123+
# Environments
124+
.env
125+
.venv
126+
env/
127+
venv/
128+
ENV/
129+
env.bak/
130+
venv.bak/
131+
132+
# Spyder project settings
133+
.spyderproject
134+
.spyproject
135+
136+
# Rope project settings
137+
.ropeproject
138+
139+
# mkdocs documentation
140+
/site
141+
142+
# mypy
143+
.mypy_cache/
144+
.dmypy.json
145+
dmypy.json
146+
147+
# Pyre type checker
148+
.pyre/
149+
150+
# pytype static type analyzer
151+
.pytype/
152+
153+
# Cython debug symbols
154+
cython_debug/
155+
156+
# PyCharm
157+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
158+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
159+
# and can be added to the global gitignore or merged into this file. For a more nuclear
160+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
161+
#.idea/
162+

Diff for: README.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Authenticate Users with FastAPI and Token Authentication
2+
3+
## Introduction
4+
5+
This is a simple project to demonstrate how to use FastAPI with OAuth2 and JWT.
6+
7+
## Requirements
8+
9+
### Create a virtual environment
10+
```
11+
python -m venv venv
12+
```
13+
14+
### Activate the virtual environment
15+
```
16+
source venv/bin/activate
17+
```
18+
19+
### Install the requirements
20+
```
21+
pip install fastapi
22+
pip install uvicorn[standard]
23+
pip install python-multipart
24+
pip install python-jose[cryptography]
25+
pip install passlib[bcrypt]
26+
```
27+
28+
## Generate a secret key
29+
```
30+
openssl rand -hex 32
31+
```
32+
33+
## Run the application
34+
```
35+
uvicorn main:app --reload
36+
```
37+
38+
## Test the application
39+
```
40+
http://localhost:8000/docs
41+
```
42+
43+
## References
44+
45+
- [Youtube](https://youtu.be/5GxQ1rLTwaU)
46+
47+
```
48+
```

Diff for: main.py

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
from fastapi import FastAPI, Depends, HTTPException, status
2+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
3+
from pydantic import BaseModel
4+
from datetime import datetime, timedelta
5+
from jose import JWTError, jwt
6+
from passlib.context import CryptContext
7+
8+
SECRET_KEY = "0ef73c3575988cdf037e6fe5216e9a217b3629f45003916b4e1f2029261670f6" # openssl rand -hex 32
9+
ALGORITHM = "HS256"
10+
ACCESS_TOKEN_EXPIRE_MINUTES = 30
11+
12+
db = {
13+
"tim": {
14+
"username": "tim",
15+
"full_name": "Timothy T. Tim",
16+
"email": "[email protected]",
17+
"hashed_password": "$2b$12$Gt/1jgwesYKAjoiQbLMaye9TjnnlXtccly9Myp5oh9A9XvpsuFNre", # password: pass
18+
"disabled": False
19+
}
20+
}
21+
22+
23+
class Token(BaseModel):
24+
access_token: str
25+
token_type: str
26+
27+
28+
class TokenData(BaseModel):
29+
username: str or None = None
30+
31+
32+
class User(BaseModel):
33+
username: str
34+
email: str or None = None
35+
full_name: str or None = None
36+
disabled: bool or None = None
37+
38+
39+
class UserInDB(User):
40+
hashed_password: str
41+
42+
43+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
44+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
45+
46+
app = FastAPI()
47+
48+
49+
def verify_password(plain_password, hashed_password):
50+
return pwd_context.verify(plain_password, hashed_password)
51+
52+
53+
def get_password_hash(password):
54+
return pwd_context.hash(password)
55+
56+
57+
def get_user(db, username: str):
58+
if username in db:
59+
user_dict = db[username]
60+
return UserInDB(**user_dict)
61+
62+
63+
def authenticate_user(db, username: str, password: str):
64+
user = get_user(db, username)
65+
if not user:
66+
return False
67+
if not verify_password(password, user.hashed_password):
68+
return False
69+
return user
70+
71+
72+
def create_access_token(data: dict, expires_delta: timedelta or None = None):
73+
to_encode = data.copy()
74+
if expires_delta:
75+
expire = datetime.utcnow() + expires_delta
76+
else:
77+
expire = datetime.utcnow() + timedelta(minutes=30)
78+
79+
to_encode.update({"exp": expire})
80+
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
81+
return encoded_jwt
82+
83+
84+
async def get_current_user(token: str = Depends(oauth2_scheme)):
85+
credentials_exception = HTTPException(
86+
status_code=status.HTTP_401_UNAUTHORIZED,
87+
detail="Invalid credentials",
88+
headers={"WWW-Authenticate": "Bearer"}
89+
)
90+
try:
91+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
92+
username: str = payload.get("sub")
93+
if username is None:
94+
raise credentials_exception
95+
token_data = TokenData(username=username)
96+
except JWTError:
97+
raise credentials_exception
98+
99+
user = get_user(db, username=token_data.username)
100+
if user is None:
101+
raise credentials_exception
102+
return user
103+
104+
105+
async def get_current_active_user(current_user: UserInDB = Depends(get_current_user)):
106+
if current_user.disabled:
107+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user")
108+
return current_user
109+
110+
111+
@app.post("/token", response_model=Token)
112+
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
113+
user = authenticate_user(db, form_data.username, form_data.password)
114+
if not user:
115+
raise HTTPException(
116+
status_code=status.HTTP_401_UNAUTHORIZED,
117+
detail="Incorrect username or password",
118+
headers={"WWW-Authenticate": "Bearer"}
119+
)
120+
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
121+
access_token = create_access_token(data={"sub": user.username}, expires_delta=access_token_expires)
122+
return {"access_token": access_token, "token_type": "bearer"}
123+
124+
125+
@app.get("/users/me", response_model=User)
126+
async def read_users_me(current_user: UserInDB = Depends(get_current_active_user)):
127+
return current_user
128+
129+
130+
@app.get("/users/me/items/")
131+
async def read_own_items(current_user: UserInDB = Depends(get_current_active_user)):
132+
return [{"item_id": 1, "owner": current_user}]

0 commit comments

Comments
 (0)