Skip to content

Oauth2๏ธโƒฃ โ†”

๐Ÿ‘† ๐Ÿ’ช โš™๏ธ Oauth2๏ธโƒฃ โ†” ๐Ÿ”— โฎ๏ธ FastAPI, ๐Ÿ‘ซ ๐Ÿ› ๏ธ ๐Ÿ‘ท ๐Ÿ’Ž.

๐Ÿ‘‰ ๐Ÿ”œ โœ” ๐Ÿ‘† โœ”๏ธ ๐ŸŒ– ๐Ÿ‘Œ-๐Ÿงฝ โœ” โš™๏ธ, ๐Ÿ“„ Oauth2๏ธโƒฃ ๐Ÿฉ, ๐Ÿ› ๏ธ ๐Ÿ”˜ ๐Ÿ‘† ๐Ÿ—„ ๐Ÿˆธ (& ๐Ÿ› ๏ธ ๐Ÿฉบ).

Oauth2๏ธโƒฃ โฎ๏ธ โ†” ๐Ÿ› ๏ธ โš™๏ธ ๐Ÿ“š ๐Ÿฆ ๐Ÿค ๐Ÿ•โ€๐Ÿฆบ, ๐Ÿ’– ๐Ÿ‘ฑ๐Ÿ“”, ๐Ÿ‡บ๐Ÿ‡ธ๐Ÿ”, ๐Ÿ“‚, ๐Ÿคธโ€โ™‚, ๐Ÿ‘ฑ๐Ÿ“”, โ™’๏ธ. ๐Ÿ‘ซ โš™๏ธ โšซ๏ธ ๐Ÿšš ๐ŸŽฏ โœ” ๐Ÿ‘ฉโ€๐Ÿ’ป & ๐Ÿˆธ.

๐Ÿ”  ๐Ÿ•ฐ ๐Ÿ‘† "๐Ÿ•น โฎ๏ธ" ๐Ÿ‘ฑ๐Ÿ“”, ๐Ÿ‡บ๐Ÿ‡ธ๐Ÿ”, ๐Ÿ“‚, ๐Ÿคธโ€โ™‚, ๐Ÿ‘ฑ๐Ÿ“”, ๐Ÿ‘ˆ ๐Ÿˆธ โš™๏ธ Oauth2๏ธโƒฃ โฎ๏ธ โ†”.

๐Ÿ‘‰ ๐Ÿ“„ ๐Ÿ‘† ๐Ÿ”œ ๐Ÿ‘€ โ” ๐Ÿ› ๏ธ ๐Ÿค & โœ” โฎ๏ธ ๐ŸŽ Oauth2๏ธโƒฃ โฎ๏ธ โ†” ๐Ÿ‘† FastAPI ๐Ÿˆธ.

Warning

๐Ÿ‘‰ ๐ŸŒ… โš–๏ธ ๐ŸŒ˜ ๐Ÿง ๐Ÿ“„. ๐Ÿšฅ ๐Ÿ‘† โ–ถ๏ธ, ๐Ÿ‘† ๐Ÿ’ช ๐Ÿšถ โšซ๏ธ.

๐Ÿ‘† ๐Ÿšซ ๐ŸŽฏ ๐Ÿ’ช Oauth2๏ธโƒฃ โ†”, & ๐Ÿ‘† ๐Ÿ’ช ๐Ÿต ๐Ÿค & โœ” ๐Ÿ‘ ๐Ÿ‘† ๐Ÿ’š.

โœ‹๏ธ Oauth2๏ธโƒฃ โฎ๏ธ โ†” ๐Ÿ’ช ๐ŸŽ† ๐Ÿ› ๏ธ ๐Ÿ”˜ ๐Ÿ‘† ๐Ÿ› ๏ธ (โฎ๏ธ ๐Ÿ—„) & ๐Ÿ‘† ๐Ÿ› ๏ธ ๐Ÿฉบ.

๐Ÿ‘, ๐Ÿ‘† ๐Ÿ› ๏ธ ๐Ÿ“š โ†”, โš–๏ธ ๐Ÿ™† ๐ŸŽ ๐Ÿ’‚โ€โ™‚/โœ” ๐Ÿ“„, ๐Ÿ‘ ๐Ÿ‘† ๐Ÿ’ช, ๐Ÿ‘† ๐Ÿ“Ÿ.

๐Ÿ“š ๐Ÿ’ผ, Oauth2๏ธโƒฃ โฎ๏ธ โ†” ๐Ÿ’ช ๐Ÿ‘น.

โœ‹๏ธ ๐Ÿšฅ ๐Ÿ‘† ๐Ÿ’ญ ๐Ÿ‘† ๐Ÿ’ช โšซ๏ธ, โš–๏ธ ๐Ÿ‘† ๐Ÿ˜Ÿ, ๐Ÿšง ๐Ÿ‘‚.

Oauth2๏ธโƒฃ โ†” & ๐Ÿ—„

Oauth2๏ธโƒฃ ๐Ÿ”ง ๐Ÿ”ฌ "โ†”" ๐Ÿ“‡ ๐ŸŽป ๐ŸŽ ๐Ÿš€.

๐ŸŽš ๐Ÿ”  ๐Ÿ‘‰ ๐ŸŽป ๐Ÿ’ช โœ”๏ธ ๐Ÿ™† ๐Ÿ“, โœ‹๏ธ ๐Ÿ”œ ๐Ÿšซ ๐Ÿ”Œ ๐Ÿš€.

๐Ÿ‘ซ โ†” ๐ŸŽจ "โœ”".

๐Ÿ—„ (โœ… ๐Ÿ› ๏ธ ๐Ÿฉบ), ๐Ÿ‘† ๐Ÿ’ช ๐Ÿ”ฌ "๐Ÿ’‚โ€โ™‚ โš–".

๐Ÿ•โ” 1๏ธโƒฃ ๐Ÿ‘ซ ๐Ÿ’‚โ€โ™‚ โš– โš™๏ธ Oauth2๏ธโƒฃ, ๐Ÿ‘† ๐Ÿ’ช ๐Ÿ“ฃ & โš™๏ธ โ†”.

๐Ÿ”  "โ†”" ๐ŸŽป (๐Ÿต ๐Ÿš€).

๐Ÿ‘ซ ๐Ÿ›Ž โš™๏ธ ๐Ÿ“ฃ ๐ŸŽฏ ๐Ÿ’‚โ€โ™‚ โœ”, ๐Ÿ–ผ:

  • users:read โš–๏ธ users:write โš  ๐Ÿ–ผ.
  • instagram_basic โš™๏ธ ๐Ÿ‘ฑ๐Ÿ“” / ๐Ÿ‘ฑ๐Ÿ“”.
  • https://www.googleapis.com/auth/drive โš™๏ธ ๐Ÿ‡บ๐Ÿ‡ธ๐Ÿ”.

Info

Oauth2๏ธโƒฃ "โ†”" ๐ŸŽป ๐Ÿ‘ˆ ๐Ÿ“ฃ ๐ŸŽฏ โœ” โœ”.

โšซ๏ธ ๐Ÿšซ ๐Ÿค” ๐Ÿšฅ โšซ๏ธ โœ”๏ธ ๐ŸŽ ๐Ÿฆน ๐Ÿ’– : โš–๏ธ ๐Ÿšฅ โšซ๏ธ ๐Ÿ“›.

๐Ÿ‘ˆ โ„น ๐Ÿ› ๏ธ ๐ŸŽฏ.

Oauth2๏ธโƒฃ ๐Ÿ‘ซ ๐ŸŽป.

๐ŸŒ ๐ŸŽ‘

๐Ÿฅ‡, โžก๏ธ ๐Ÿ”œ ๐Ÿ‘€ ๐Ÿ• ๐Ÿ‘ˆ ๐Ÿ”€ โšช๏ธโžก๏ธ ๐Ÿ–ผ ๐Ÿ‘‘ ๐Ÿ”ฐ - ๐Ÿ‘ฉโ€๐Ÿ’ป ๐Ÿฆฎ Oauth2๏ธโƒฃ โฎ๏ธ ๐Ÿ” (& ๐Ÿ”), ๐Ÿ“จ โฎ๏ธ ๐Ÿฅ™ ๐Ÿค. ๐Ÿ”œ โš™๏ธ Oauth2๏ธโƒฃ โ†”:

from datetime import datetime, timedelta, timezone
from typing import List, Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import (
    OAuth2PasswordBearer,
    OAuth2PasswordRequestForm,
    SecurityScopes,
)
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError

# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30


fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Chains",
        "email": "alicechains@example.com",
        "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
        "disabled": True,
    },
}


class Token(BaseModel):
    access_token: str
    token_type: str


class TokenData(BaseModel):
    username: Union[str, None] = None
    scopes: List[str] = []


class User(BaseModel):
    username: str
    email: Union[str, None] = None
    full_name: Union[str, None] = None
    disabled: Union[bool, None] = None


class UserInDB(User):
    hashed_password: str


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="token",
    scopes={"me": "Read information about the current user.", "items": "Read items."},
)

app = FastAPI()


def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)


def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user


def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


async def get_current_user(
    security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
):
    if security_scopes.scopes:
        authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
    else:
        authenticate_value = "Bearer"
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": authenticate_value},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_scopes = payload.get("scopes", [])
        token_data = TokenData(scopes=token_scopes, username=username)
    except (JWTError, ValidationError):
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    for scope in security_scopes.scopes:
        if scope not in token_data.scopes:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Not enough permissions",
                headers={"WWW-Authenticate": authenticate_value},
            )
    return user


async def get_current_active_user(
    current_user: User = Security(get_current_user, scopes=["me"])
):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username, "scopes": form_data.scopes},
        expires_delta=access_token_expires,
    )
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


@app.get("/users/me/items/")
async def read_own_items(
    current_user: User = Security(get_current_active_user, scopes=["items"])
):
    return [{"item_id": "Foo", "owner": current_user.username}]


@app.get("/status/")
async def read_system_status(current_user: User = Depends(get_current_user)):
    return {"status": "ok"}

๐Ÿ”œ โžก๏ธ ๐Ÿ“„ ๐Ÿ‘ˆ ๐Ÿ”€ ๐Ÿ” ๐Ÿ”.

Oauth2๏ธโƒฃ ๐Ÿ’‚โ€โ™‚ โš–

๐Ÿฅ‡ ๐Ÿ”€ ๐Ÿ‘ˆ ๐Ÿ”œ ๐Ÿ‘ฅ ๐Ÿ“ฃ Oauth2๏ธโƒฃ ๐Ÿ’‚โ€โ™‚ โš– โฎ๏ธ 2๏ธโƒฃ ๐Ÿ’ช โ†”, me & items.

scopes ๐Ÿ”ข ๐Ÿ“จ dict โฎ๏ธ ๐Ÿ”  โ†” ๐Ÿ”‘ & ๐Ÿ“› ๐Ÿ’ฒ:

from datetime import datetime, timedelta, timezone
from typing import List, Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import (
    OAuth2PasswordBearer,
    OAuth2PasswordRequestForm,
    SecurityScopes,
)
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError

# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30


fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Chains",
        "email": "alicechains@example.com",
        "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
        "disabled": True,
    },
}


class Token(BaseModel):
    access_token: str
    token_type: str


class TokenData(BaseModel):
    username: Union[str, None] = None
    scopes: List[str] = []


class User(BaseModel):
    username: str
    email: Union[str, None] = None
    full_name: Union[str, None] = None
    disabled: Union[bool, None] = None


class UserInDB(User):
    hashed_password: str


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="token",
    scopes={"me": "Read information about the current user.", "items": "Read items."},
)

app = FastAPI()


def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)


def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user


def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


async def get_current_user(
    security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
):
    if security_scopes.scopes:
        authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
    else:
        authenticate_value = "Bearer"
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": authenticate_value},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_scopes = payload.get("scopes", [])
        token_data = TokenData(scopes=token_scopes, username=username)
    except (JWTError, ValidationError):
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    for scope in security_scopes.scopes:
        if scope not in token_data.scopes:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Not enough permissions",
                headers={"WWW-Authenticate": authenticate_value},
            )
    return user


async def get_current_active_user(
    current_user: User = Security(get_current_user, scopes=["me"])
):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username, "scopes": form_data.scopes},
        expires_delta=access_token_expires,
    )
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


@app.get("/users/me/items/")
async def read_own_items(
    current_user: User = Security(get_current_active_user, scopes=["items"])
):
    return [{"item_id": "Foo", "owner": current_user.username}]


@app.get("/status/")
async def read_system_status(current_user: User = Depends(get_current_user)):
    return {"status": "ok"}

โ†ฉ๏ธ ๐Ÿ‘ฅ ๐Ÿ”œ ๐Ÿ“ฃ ๐Ÿ“š โ†”, ๐Ÿ‘ซ ๐Ÿ”œ ๐ŸŽฆ ๐Ÿ†™ ๐Ÿ› ๏ธ ๐Ÿฉบ ๐Ÿ•โ” ๐Ÿ‘† ๐Ÿ•น-/โœ”.

& ๐Ÿ‘† ๐Ÿ”œ ๐Ÿ’ช ๐Ÿ–Š โ” โ†” ๐Ÿ‘† ๐Ÿ’š ๐Ÿค ๐Ÿ”: me & items.

๐Ÿ‘‰ ๐ŸŽ ๐Ÿ› ๏ธ โš™๏ธ ๐Ÿ•โ” ๐Ÿ‘† ๐Ÿค โœ” โช ๐Ÿšจ โฎ๏ธ ๐Ÿ‘ฑ๐Ÿ“”, ๐Ÿ‡บ๐Ÿ‡ธ๐Ÿ”, ๐Ÿ“‚, โ™’๏ธ:

๐Ÿฅ™ ๐Ÿค โฎ๏ธ โ†”

๐Ÿ”œ, ๐Ÿ”€ ๐Ÿค โžก ๐Ÿ› ๏ธ ๐Ÿ“จ โ†” ๐Ÿ“จ.

๐Ÿ‘ฅ โš™๏ธ ๐ŸŽ OAuth2PasswordRequestForm. โšซ๏ธ ๐Ÿ”Œ ๐Ÿ  scopes โฎ๏ธ list str, โฎ๏ธ ๐Ÿ”  โ†” โšซ๏ธ ๐Ÿ“จ ๐Ÿ“จ.

& ๐Ÿ‘ฅ ๐Ÿ“จ โ†” ๐Ÿ• ๐Ÿฅ™ ๐Ÿค.

Danger

๐Ÿฆ, ๐Ÿ“ฅ ๐Ÿ‘ฅ โŽ โ†” ๐Ÿ“จ ๐Ÿ”— ๐Ÿค.

โœ‹๏ธ ๐Ÿ‘† ๐Ÿˆธ, ๐Ÿ’‚โ€โ™‚, ๐Ÿ‘† ๐Ÿ”œ โš’ ๐Ÿ’ญ ๐Ÿ‘† ๐Ÿ•ด ๐Ÿšฎ โ†” ๐Ÿ‘ˆ ๐Ÿ‘ฉโ€๐Ÿ’ป ๐Ÿค™ ๐Ÿ’ช โœ”๏ธ, โš–๏ธ ๐Ÿ• ๐Ÿ‘† โœ”๏ธ ๐Ÿ”.

from datetime import datetime, timedelta, timezone
from typing import List, Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import (
    OAuth2PasswordBearer,
    OAuth2PasswordRequestForm,
    SecurityScopes,
)
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError

# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30


fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Chains",
        "email": "alicechains@example.com",
        "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
        "disabled": True,
    },
}


class Token(BaseModel):
    access_token: str
    token_type: str


class TokenData(BaseModel):
    username: Union[str, None] = None
    scopes: List[str] = []


class User(BaseModel):
    username: str
    email: Union[str, None] = None
    full_name: Union[str, None] = None
    disabled: Union[bool, None] = None


class UserInDB(User):
    hashed_password: str


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="token",
    scopes={"me": "Read information about the current user.", "items": "Read items."},
)

app = FastAPI()


def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)


def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user


def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


async def get_current_user(
    security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
):
    if security_scopes.scopes:
        authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
    else:
        authenticate_value = "Bearer"
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": authenticate_value},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_scopes = payload.get("scopes", [])
        token_data = TokenData(scopes=token_scopes, username=username)
    except (JWTError, ValidationError):
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    for scope in security_scopes.scopes:
        if scope not in token_data.scopes:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Not enough permissions",
                headers={"WWW-Authenticate": authenticate_value},
            )
    return user


async def get_current_active_user(
    current_user: User = Security(get_current_user, scopes=["me"])
):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username, "scopes": form_data.scopes},
        expires_delta=access_token_expires,
    )
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


@app.get("/users/me/items/")
async def read_own_items(
    current_user: User = Security(get_current_active_user, scopes=["items"])
):
    return [{"item_id": "Foo", "owner": current_user.username}]


@app.get("/status/")
async def read_system_status(current_user: User = Depends(get_current_user)):
    return {"status": "ok"}

๐Ÿ“ฃ โ†” โžก ๐Ÿ› ๏ธ & ๐Ÿ”—

๐Ÿ”œ ๐Ÿ‘ฅ ๐Ÿ“ฃ ๐Ÿ‘ˆ โžก ๐Ÿ› ๏ธ /users/me/items/ ๐Ÿšš โ†” items.

๐Ÿ‘‰, ๐Ÿ‘ฅ ๐Ÿ—„ & โš™๏ธ Security โšช๏ธโžก๏ธ fastapi.

๐Ÿ‘† ๐Ÿ’ช โš™๏ธ Security ๐Ÿ“ฃ ๐Ÿ”— (๐Ÿ’– Depends), โœ‹๏ธ Security ๐Ÿ“จ ๐Ÿ”ข scopes โฎ๏ธ ๐Ÿ“‡ โ†” (๐ŸŽป).

๐Ÿ‘‰ ๐Ÿ’ผ, ๐Ÿ‘ฅ ๐Ÿšถโ€โ™€๏ธ ๐Ÿ”— ๐Ÿ”ข get_current_active_user Security (๐ŸŽ ๐ŸŒŒ ๐Ÿ‘ฅ ๐Ÿ”œ โฎ๏ธ Depends).

โœ‹๏ธ ๐Ÿ‘ฅ ๐Ÿšถโ€โ™€๏ธ list โ†”, ๐Ÿ‘‰ ๐Ÿ’ผ โฎ๏ธ 1๏ธโƒฃ โ†”: items (โšซ๏ธ ๐Ÿ’ช โœ”๏ธ ๐ŸŒ…).

& ๐Ÿ”— ๐Ÿ”ข get_current_active_user ๐Ÿ’ช ๐Ÿ“ฃ ๐ŸŽง-๐Ÿ”—, ๐Ÿšซ ๐Ÿ•ด โฎ๏ธ Depends โœ‹๏ธ โฎ๏ธ Security. ๐Ÿ“ฃ ๐Ÿšฎ ๐Ÿ‘ ๐ŸŽง-๐Ÿ”— ๐Ÿ”ข (get_current_user), & ๐ŸŒ– โ†” ๐Ÿ“„.

๐Ÿ‘‰ ๐Ÿ’ผ, โšซ๏ธ ๐Ÿšš โ†” me (โšซ๏ธ ๐Ÿ’ช ๐Ÿšš ๐ŸŒ… ๐ŸŒ˜ 1๏ธโƒฃ โ†”).

Note

๐Ÿ‘† ๐Ÿšซ ๐ŸŽฏ ๐Ÿ’ช ๐Ÿšฎ ๐ŸŽ โ†” ๐ŸŽ ๐Ÿฅ‰.

๐Ÿ‘ฅ ๐Ÿ”จ โšซ๏ธ ๐Ÿ“ฅ ๐ŸŽฆ โ” FastAPI ๐Ÿต โ†” ๐Ÿ“ฃ ๐ŸŽ ๐ŸŽš.

from datetime import datetime, timedelta, timezone
from typing import List, Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import (
    OAuth2PasswordBearer,
    OAuth2PasswordRequestForm,
    SecurityScopes,
)
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError

# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30


fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Chains",
        "email": "alicechains@example.com",
        "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
        "disabled": True,
    },
}


class Token(BaseModel):
    access_token: str
    token_type: str


class TokenData(BaseModel):
    username: Union[str, None] = None
    scopes: List[str] = []


class User(BaseModel):
    username: str
    email: Union[str, None] = None
    full_name: Union[str, None] = None
    disabled: Union[bool, None] = None


class UserInDB(User):
    hashed_password: str


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="token",
    scopes={"me": "Read information about the current user.", "items": "Read items."},
)

app = FastAPI()


def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)


def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user


def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


async def get_current_user(
    security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
):
    if security_scopes.scopes:
        authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
    else:
        authenticate_value = "Bearer"
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": authenticate_value},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_scopes = payload.get("scopes", [])
        token_data = TokenData(scopes=token_scopes, username=username)
    except (JWTError, ValidationError):
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    for scope in security_scopes.scopes:
        if scope not in token_data.scopes:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Not enough permissions",
                headers={"WWW-Authenticate": authenticate_value},
            )
    return user


async def get_current_active_user(
    current_user: User = Security(get_current_user, scopes=["me"])
):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username, "scopes": form_data.scopes},
        expires_delta=access_token_expires,
    )
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


@app.get("/users/me/items/")
async def read_own_items(
    current_user: User = Security(get_current_active_user, scopes=["items"])
):
    return [{"item_id": "Foo", "owner": current_user.username}]


@app.get("/status/")
async def read_system_status(current_user: User = Depends(get_current_user)):
    return {"status": "ok"}

๐Ÿ“ก โ„น

Security ๐Ÿค™ ๐Ÿฟ Depends, & โšซ๏ธ โœ”๏ธ 1๏ธโƒฃ โž• ๐Ÿ”ข ๐Ÿ‘ˆ ๐Ÿ‘ฅ ๐Ÿ”œ ๐Ÿ‘€ โช.

โœ‹๏ธ โš™๏ธ Security โ†ฉ๏ธ Depends, FastAPI ๐Ÿ”œ ๐Ÿ’ญ ๐Ÿ‘ˆ โšซ๏ธ ๐Ÿ’ช ๐Ÿ“ฃ ๐Ÿ’‚โ€โ™‚ โ†”, โš™๏ธ ๐Ÿ‘ซ ๐Ÿ”˜, & ๐Ÿ“„ ๐Ÿ› ๏ธ โฎ๏ธ ๐Ÿ—„.

โœ‹๏ธ ๐Ÿ•โ” ๐Ÿ‘† ๐Ÿ—„ Query, Path, Depends, Security & ๐ŸŽ โšช๏ธโžก๏ธ fastapi, ๐Ÿ‘ˆ ๐Ÿค™ ๐Ÿ”ข ๐Ÿ‘ˆ ๐Ÿ“จ ๐ŸŽ ๐ŸŽ“.

โš™๏ธ SecurityScopes

๐Ÿ”œ โ„น ๐Ÿ”— get_current_user.

๐Ÿ‘‰ 1๏ธโƒฃ โš™๏ธ ๐Ÿ”— ๐Ÿ”›.

๐Ÿ“ฅ ๐Ÿ‘ฅ โš™๏ธ ๐ŸŽ Oauth2๏ธโƒฃ โš– ๐Ÿ‘ฅ โœ โญ, ๐Ÿ“ฃ โšซ๏ธ ๐Ÿ”—: oauth2_scheme.

โ†ฉ๏ธ ๐Ÿ‘‰ ๐Ÿ”— ๐Ÿ”ข ๐Ÿšซ โœ”๏ธ ๐Ÿ™† โ†” ๐Ÿ“„ โšซ๏ธ, ๐Ÿ‘ฅ ๐Ÿ’ช โš™๏ธ Depends โฎ๏ธ oauth2_scheme, ๐Ÿ‘ฅ ๐Ÿšซ โœ”๏ธ โš™๏ธ Security ๐Ÿ•โ” ๐Ÿ‘ฅ ๐Ÿšซ ๐Ÿ’ช โœ” ๐Ÿ’‚โ€โ™‚ โ†”.

๐Ÿ‘ฅ ๐Ÿ“ฃ ๐ŸŽ ๐Ÿ”ข ๐Ÿ†Ž SecurityScopes, ๐Ÿ—„ โšช๏ธโžก๏ธ fastapi.security.

๐Ÿ‘‰ SecurityScopes ๐ŸŽ“ ๐ŸŽ Request (Request โš™๏ธ ๐Ÿคš ๐Ÿ“จ ๐ŸŽš ๐Ÿ”—).

from datetime import datetime, timedelta, timezone
from typing import List, Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import (
    OAuth2PasswordBearer,
    OAuth2PasswordRequestForm,
    SecurityScopes,
)
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError

# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30


fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Chains",
        "email": "alicechains@example.com",
        "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
        "disabled": True,
    },
}


class Token(BaseModel):
    access_token: str
    token_type: str


class TokenData(BaseModel):
    username: Union[str, None] = None
    scopes: List[str] = []


class User(BaseModel):
    username: str
    email: Union[str, None] = None
    full_name: Union[str, None] = None
    disabled: Union[bool, None] = None


class UserInDB(User):
    hashed_password: str


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="token",
    scopes={"me": "Read information about the current user.", "items": "Read items."},
)

app = FastAPI()


def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)


def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user


def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


async def get_current_user(
    security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
):
    if security_scopes.scopes:
        authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
    else:
        authenticate_value = "Bearer"
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": authenticate_value},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_scopes = payload.get("scopes", [])
        token_data = TokenData(scopes=token_scopes, username=username)
    except (JWTError, ValidationError):
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    for scope in security_scopes.scopes:
        if scope not in token_data.scopes:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Not enough permissions",
                headers={"WWW-Authenticate": authenticate_value},
            )
    return user


async def get_current_active_user(
    current_user: User = Security(get_current_user, scopes=["me"])
):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username, "scopes": form_data.scopes},
        expires_delta=access_token_expires,
    )
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


@app.get("/users/me/items/")
async def read_own_items(
    current_user: User = Security(get_current_active_user, scopes=["items"])
):
    return [{"item_id": "Foo", "owner": current_user.username}]


@app.get("/status/")
async def read_system_status(current_user: User = Depends(get_current_user)):
    return {"status": "ok"}

โš™๏ธ scopes

๐Ÿ”ข security_scopes ๐Ÿ”œ ๐Ÿ†Ž SecurityScopes.

โšซ๏ธ ๐Ÿ”œ โœ”๏ธ ๐Ÿ  scopes โฎ๏ธ ๐Ÿ“‡ โš— ๐ŸŒ โ†” โœ” โšซ๏ธ & ๐ŸŒ ๐Ÿ”— ๐Ÿ‘ˆ โš™๏ธ ๐Ÿ‘‰ ๐ŸŽง-๐Ÿ”—. ๐Ÿ‘ˆ โ›“, ๐ŸŒ "โš“๏ธ"... ๐Ÿ‘‰ ๐Ÿ’ช ๐Ÿ”Š ๐Ÿ˜จ, โšซ๏ธ ๐Ÿ”ฌ ๐Ÿ”„ โช ๐Ÿ”›.

security_scopes ๐ŸŽš (๐ŸŽ“ SecurityScopes) ๐Ÿšš scope_str ๐Ÿ”ข โฎ๏ธ ๐Ÿ‘ ๐ŸŽป, ๐Ÿ”Œ ๐Ÿ‘ˆ โ†” ๐Ÿ‘ฝ ๐Ÿš€ (๐Ÿ‘ฅ ๐Ÿ”œ โš™๏ธ โšซ๏ธ).

๐Ÿ‘ฅ โœ HTTPException ๐Ÿ‘ˆ ๐Ÿ‘ฅ ๐Ÿ’ช ๐Ÿค-โš™๏ธ (raise) โช ๐Ÿ“š โ˜.

๐Ÿ‘‰ โš , ๐Ÿ‘ฅ ๐Ÿ”Œ โ†” ๐Ÿšš (๐Ÿšฅ ๐Ÿ™†) ๐ŸŽป ๐Ÿ‘ฝ ๐Ÿš€ (โš™๏ธ scope_str). ๐Ÿ‘ฅ ๐Ÿšฎ ๐Ÿ‘ˆ ๐ŸŽป โš— โ†” WWW-Authenticate ๐ŸŽš (๐Ÿ‘‰ ๐Ÿ• ๐Ÿ”Œ).

from datetime import datetime, timedelta, timezone
from typing import List, Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import (
    OAuth2PasswordBearer,
    OAuth2PasswordRequestForm,
    SecurityScopes,
)
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError

# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30


fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Chains",
        "email": "alicechains@example.com",
        "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
        "disabled": True,
    },
}


class Token(BaseModel):
    access_token: str
    token_type: str


class TokenData(BaseModel):
    username: Union[str, None] = None
    scopes: List[str] = []


class User(BaseModel):
    username: str
    email: Union[str, None] = None
    full_name: Union[str, None] = None
    disabled: Union[bool, None] = None


class UserInDB(User):
    hashed_password: str


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="token",
    scopes={"me": "Read information about the current user.", "items": "Read items."},
)

app = FastAPI()


def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)


def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user


def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


async def get_current_user(
    security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
):
    if security_scopes.scopes:
        authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
    else:
        authenticate_value = "Bearer"
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": authenticate_value},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_scopes = payload.get("scopes", [])
        token_data = TokenData(scopes=token_scopes, username=username)
    except (JWTError, ValidationError):
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    for scope in security_scopes.scopes:
        if scope not in token_data.scopes:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Not enough permissions",
                headers={"WWW-Authenticate": authenticate_value},
            )
    return user


async def get_current_active_user(
    current_user: User = Security(get_current_user, scopes=["me"])
):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username, "scopes": form_data.scopes},
        expires_delta=access_token_expires,
    )
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


@app.get("/users/me/items/")
async def read_own_items(
    current_user: User = Security(get_current_active_user, scopes=["items"])
):
    return [{"item_id": "Foo", "owner": current_user.username}]


@app.get("/status/")
async def read_system_status(current_user: User = Depends(get_current_user)):
    return {"status": "ok"}

โœ” username & ๐Ÿ’ฝ ๐Ÿ’ 

๐Ÿ‘ฅ โœ” ๐Ÿ‘ˆ ๐Ÿ‘ฅ ๐Ÿคš username, & โš— โ†”.

& โคด๏ธ ๐Ÿ‘ฅ โœ” ๐Ÿ‘ˆ ๐Ÿ“Š โฎ๏ธ Pydantic ๐Ÿท (โœŠ ValidationError โš ), & ๐Ÿšฅ ๐Ÿ‘ฅ ๐Ÿคš โŒ ๐Ÿ‘‚ ๐Ÿฅ™ ๐Ÿค โš–๏ธ โš– ๐Ÿ“Š โฎ๏ธ Pydantic, ๐Ÿ‘ฅ ๐Ÿคš HTTPException ๐Ÿ‘ฅ โœ โญ.

๐Ÿ‘ˆ, ๐Ÿ‘ฅ โ„น Pydantic ๐Ÿท TokenData โฎ๏ธ ๐Ÿ†• ๐Ÿ  scopes.

โš– ๐Ÿ“Š โฎ๏ธ Pydantic ๐Ÿ‘ฅ ๐Ÿ’ช โš’ ๐Ÿ’ญ ๐Ÿ‘ˆ ๐Ÿ‘ฅ โœ”๏ธ, ๐Ÿ–ผ, โšซ๏ธโ” list str โฎ๏ธ โ†” & str โฎ๏ธ username.

โ†ฉ๏ธ, ๐Ÿ–ผ, dict, โš–๏ธ ๐Ÿ•ณ ๐Ÿ™†, โšซ๏ธ ๐Ÿ’ช ๐Ÿ’” ๐Ÿˆธ โ˜ โช, โš’ โšซ๏ธ ๐Ÿ’‚โ€โ™‚ โš .

๐Ÿ‘ฅ โœ” ๐Ÿ‘ˆ ๐Ÿ‘ฅ โœ”๏ธ ๐Ÿ‘ฉโ€๐Ÿ’ป โฎ๏ธ ๐Ÿ‘ˆ ๐Ÿ†”, & ๐Ÿšฅ ๐Ÿšซ, ๐Ÿ‘ฅ ๐Ÿคš ๐Ÿ‘ˆ ๐ŸŽ โš  ๐Ÿ‘ฅ โœ โญ.

from datetime import datetime, timedelta, timezone
from typing import List, Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import (
    OAuth2PasswordBearer,
    OAuth2PasswordRequestForm,
    SecurityScopes,
)
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError

# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30


fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Chains",
        "email": "alicechains@example.com",
        "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
        "disabled": True,
    },
}


class Token(BaseModel):
    access_token: str
    token_type: str


class TokenData(BaseModel):
    username: Union[str, None] = None
    scopes: List[str] = []


class User(BaseModel):
    username: str
    email: Union[str, None] = None
    full_name: Union[str, None] = None
    disabled: Union[bool, None] = None


class UserInDB(User):
    hashed_password: str


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="token",
    scopes={"me": "Read information about the current user.", "items": "Read items."},
)

app = FastAPI()


def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)


def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user


def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


async def get_current_user(
    security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
):
    if security_scopes.scopes:
        authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
    else:
        authenticate_value = "Bearer"
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": authenticate_value},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_scopes = payload.get("scopes", [])
        token_data = TokenData(scopes=token_scopes, username=username)
    except (JWTError, ValidationError):
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    for scope in security_scopes.scopes:
        if scope not in token_data.scopes:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Not enough permissions",
                headers={"WWW-Authenticate": authenticate_value},
            )
    return user


async def get_current_active_user(
    current_user: User = Security(get_current_user, scopes=["me"])
):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username, "scopes": form_data.scopes},
        expires_delta=access_token_expires,
    )
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


@app.get("/users/me/items/")
async def read_own_items(
    current_user: User = Security(get_current_active_user, scopes=["items"])
):
    return [{"item_id": "Foo", "owner": current_user.username}]


@app.get("/status/")
async def read_system_status(current_user: User = Depends(get_current_user)):
    return {"status": "ok"}

โœ” scopes

๐Ÿ‘ฅ ๐Ÿ”œ โœ” ๐Ÿ‘ˆ ๐ŸŒ โ†” โœ”, ๐Ÿ‘‰ ๐Ÿ”— & ๐ŸŒ โš“๏ธ (๐Ÿ”Œ โžก ๐Ÿ› ๏ธ), ๐Ÿ”Œ โ†” ๐Ÿšš ๐Ÿค ๐Ÿ“จ, โช ๐Ÿคš HTTPException.

๐Ÿ‘‰, ๐Ÿ‘ฅ โš™๏ธ security_scopes.scopes, ๐Ÿ‘ˆ ๐Ÿ”Œ list โฎ๏ธ ๐ŸŒ ๐Ÿ‘ซ โ†” str.

from datetime import datetime, timedelta, timezone
from typing import List, Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import (
    OAuth2PasswordBearer,
    OAuth2PasswordRequestForm,
    SecurityScopes,
)
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError

# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30


fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Chains",
        "email": "alicechains@example.com",
        "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
        "disabled": True,
    },
}


class Token(BaseModel):
    access_token: str
    token_type: str


class TokenData(BaseModel):
    username: Union[str, None] = None
    scopes: List[str] = []


class User(BaseModel):
    username: str
    email: Union[str, None] = None
    full_name: Union[str, None] = None
    disabled: Union[bool, None] = None


class UserInDB(User):
    hashed_password: str


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="token",
    scopes={"me": "Read information about the current user.", "items": "Read items."},
)

app = FastAPI()


def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)


def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user


def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


async def get_current_user(
    security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
):
    if security_scopes.scopes:
        authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
    else:
        authenticate_value = "Bearer"
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": authenticate_value},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_scopes = payload.get("scopes", [])
        token_data = TokenData(scopes=token_scopes, username=username)
    except (JWTError, ValidationError):
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    for scope in security_scopes.scopes:
        if scope not in token_data.scopes:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Not enough permissions",
                headers={"WWW-Authenticate": authenticate_value},
            )
    return user


async def get_current_active_user(
    current_user: User = Security(get_current_user, scopes=["me"])
):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username, "scopes": form_data.scopes},
        expires_delta=access_token_expires,
    )
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


@app.get("/users/me/items/")
async def read_own_items(
    current_user: User = Security(get_current_active_user, scopes=["items"])
):
    return [{"item_id": "Foo", "owner": current_user.username}]


@app.get("/status/")
async def read_system_status(current_user: User = Depends(get_current_user)):
    return {"status": "ok"}

๐Ÿ”— ๐ŸŒฒ & โ†”

โžก๏ธ ๐Ÿ“„ ๐Ÿ”„ ๐Ÿ‘‰ ๐Ÿ”— ๐ŸŒฒ & โ†”.

get_current_active_user ๐Ÿ”— โœ”๏ธ ๐ŸŽง-๐Ÿ”— ๐Ÿ”› get_current_user, โ†” "me" ๐Ÿ“ฃ get_current_active_user ๐Ÿ”œ ๐Ÿ”Œ ๐Ÿ“‡ โœ” โ†” security_scopes.scopes ๐Ÿšถโ€โ™€๏ธ get_current_user.

โžก ๐Ÿ› ๏ธ โšซ๏ธ ๐Ÿ“ฃ โ†”, "items", ๐Ÿ‘‰ ๐Ÿ”œ ๐Ÿ“‡ security_scopes.scopes ๐Ÿšถโ€โ™€๏ธ get_current_user.

๐Ÿ“ฅ โ” ๐Ÿ”— ๐Ÿ”— & โ†” ๐Ÿ‘€ ๐Ÿ’–:

  • โžก ๐Ÿ› ๏ธ read_own_items โœ”๏ธ:
    • โœ” โ†” ["items"] โฎ๏ธ ๐Ÿ”—:
    • get_current_active_user:
      • ๐Ÿ”— ๐Ÿ”ข get_current_active_user โœ”๏ธ:
        • โœ” โ†” ["me"] โฎ๏ธ ๐Ÿ”—:
        • get_current_user:
          • ๐Ÿ”— ๐Ÿ”ข get_current_user โœ”๏ธ:
            • ๐Ÿ™…โ€โ™‚ โ†” โœ” โšซ๏ธ.
            • ๐Ÿ”— โš™๏ธ oauth2_scheme.
            • security_scopes ๐Ÿ”ข ๐Ÿ†Ž SecurityScopes:
              • ๐Ÿ‘‰ security_scopes ๐Ÿ”ข โœ”๏ธ ๐Ÿ  scopes โฎ๏ธ list โš— ๐ŸŒ ๐Ÿ‘ซ โ†” ๐Ÿ“ฃ ๐Ÿ”›,:
                • security_scopes.scopes ๐Ÿ”œ ๐Ÿ”Œ ["me", "items"] โžก ๐Ÿ› ๏ธ read_own_items.
                • security_scopes.scopes ๐Ÿ”œ ๐Ÿ”Œ ["me"] โžก ๐Ÿ› ๏ธ read_users_me, โ†ฉ๏ธ โšซ๏ธ ๐Ÿ“ฃ ๐Ÿ”— get_current_active_user.
                • security_scopes.scopes ๐Ÿ”œ ๐Ÿ”Œ [] (๐Ÿ•ณ) โžก ๐Ÿ› ๏ธ read_system_status, โ†ฉ๏ธ โšซ๏ธ ๐Ÿšซ ๐Ÿ“ฃ ๐Ÿ™† Security โฎ๏ธ scopes, & ๐Ÿšฎ ๐Ÿ”—, get_current_user, ๐Ÿšซ ๐Ÿ“ฃ ๐Ÿ™† scope ๐Ÿ‘ฏโ€โ™‚๏ธ.

Tip

โš  & "๐ŸŽฑ" ๐Ÿ‘œ ๐Ÿ“ฅ ๐Ÿ‘ˆ get_current_user ๐Ÿ”œ โœ”๏ธ ๐ŸŽ ๐Ÿ“‡ scopes โœ… ๐Ÿ”  โžก ๐Ÿ› ๏ธ.

๐ŸŒ โš“๏ธ ๐Ÿ”› scopes ๐Ÿ“ฃ ๐Ÿ”  โžก ๐Ÿ› ๏ธ & ๐Ÿ”  ๐Ÿ”— ๐Ÿ”— ๐ŸŒฒ ๐Ÿ‘ˆ ๐ŸŽฏ โžก ๐Ÿ› ๏ธ.

๐ŸŒ– โ„น ๐Ÿ”ƒ SecurityScopes

๐Ÿ‘† ๐Ÿ’ช โš™๏ธ SecurityScopes ๐Ÿ™† โ˜, & ๐Ÿ’— ๐Ÿฅ‰, โšซ๏ธ ๐Ÿšซ โœ”๏ธ "๐ŸŒฑ" ๐Ÿ”—.

โšซ๏ธ ๐Ÿ”œ ๐Ÿ•ง โœ”๏ธ ๐Ÿ’‚โ€โ™‚ โ†” ๐Ÿ“ฃ โฎ๏ธ Security ๐Ÿ”— & ๐ŸŒ โš“๏ธ ๐Ÿ‘ˆ ๐ŸŽฏ โžก ๐Ÿ› ๏ธ & ๐Ÿ‘ˆ ๐ŸŽฏ ๐Ÿ”— ๐ŸŒฒ.

โ†ฉ๏ธ SecurityScopes ๐Ÿ”œ โœ”๏ธ ๐ŸŒ โ†” ๐Ÿ“ฃ โš“๏ธ, ๐Ÿ‘† ๐Ÿ’ช โš™๏ธ โšซ๏ธ โœ” ๐Ÿ‘ˆ ๐Ÿค โœ”๏ธ ๐Ÿšš โ†” ๐Ÿ‡จ๐Ÿ‡ซ ๐Ÿ”— ๐Ÿ”ข, & โคด๏ธ ๐Ÿ“ฃ ๐ŸŽ โ†” ๐Ÿ“„ ๐ŸŽ โžก ๐Ÿ› ๏ธ.

๐Ÿ‘ซ ๐Ÿ”œ โœ… โžก ๐Ÿ”  โžก ๐Ÿ› ๏ธ.

โœ… โšซ๏ธ

๐Ÿšฅ ๐Ÿ‘† ๐Ÿ“‚ ๐Ÿ› ๏ธ ๐Ÿฉบ, ๐Ÿ‘† ๐Ÿ’ช ๐Ÿ”“ & โœ” โ” โ†” ๐Ÿ‘† ๐Ÿ’š โœ”.

๐Ÿšฅ ๐Ÿ‘† ๐Ÿšซ ๐Ÿ–Š ๐Ÿ™† โ†”, ๐Ÿ‘† ๐Ÿ”œ "๐Ÿ”“", โœ‹๏ธ ๐Ÿ•โ” ๐Ÿ‘† ๐Ÿ”„ ๐Ÿ” /users/me/ โš–๏ธ /users/me/items/ ๐Ÿ‘† ๐Ÿ”œ ๐Ÿคš โŒ ๐Ÿ’ฌ ๐Ÿ‘ˆ ๐Ÿ‘† ๐Ÿšซ โœ”๏ธ ๐Ÿฅƒ โœ”. ๐Ÿ‘† ๐Ÿ”œ ๐Ÿ’ช ๐Ÿ” /status/.

& ๐Ÿšฅ ๐Ÿ‘† ๐Ÿ–Š โ†” me โœ‹๏ธ ๐Ÿšซ โ†” items, ๐Ÿ‘† ๐Ÿ”œ ๐Ÿ’ช ๐Ÿ” /users/me/ โœ‹๏ธ ๐Ÿšซ /users/me/items/.

๐Ÿ‘ˆ โšซ๏ธโ” ๐Ÿ”œ ๐Ÿ”จ ๐Ÿฅ‰ ๐Ÿฅณ ๐Ÿˆธ ๐Ÿ‘ˆ ๐Ÿ”„ ๐Ÿ” 1๏ธโƒฃ ๐Ÿ‘ซ โžก ๐Ÿ› ๏ธ โฎ๏ธ ๐Ÿค ๐Ÿšš ๐Ÿ‘ฉโ€๐Ÿ’ป, โš“๏ธ ๐Ÿ”› โ” ๐Ÿ“š โœ” ๐Ÿ‘ฉโ€๐Ÿ’ป ๐Ÿค ๐Ÿˆธ.

๐Ÿ”ƒ ๐Ÿฅ‰ ๐Ÿฅณ ๐Ÿ› ๏ธ

๐Ÿ‘‰ ๐Ÿ–ผ ๐Ÿ‘ฅ โš™๏ธ Oauth2๏ธโƒฃ "๐Ÿ”" ๐Ÿ’ง.

๐Ÿ‘‰ โ˜‘ ๐Ÿ•โ” ๐Ÿ‘ฅ ๐Ÿšจ ๐Ÿ‘† ๐Ÿ‘ ๐Ÿˆธ, ๐ŸŽฒ โฎ๏ธ ๐Ÿ‘† ๐Ÿ‘ ๐Ÿ•ธ.

โ†ฉ๏ธ ๐Ÿ‘ฅ ๐Ÿ’ช ๐Ÿ’™ โšซ๏ธ ๐Ÿ“จ username & password, ๐Ÿ‘ฅ ๐ŸŽ› โšซ๏ธ.

โœ‹๏ธ ๐Ÿšฅ ๐Ÿ‘† ๐Ÿ— Oauth2๏ธโƒฃ ๐Ÿˆธ ๐Ÿ‘ˆ ๐ŸŽ ๐Ÿ”œ ๐Ÿ”— (โžก, ๐Ÿšฅ ๐Ÿ‘† ๐Ÿ— ๐Ÿค ๐Ÿ•โ€๐Ÿฆบ ๐ŸŒ“ ๐Ÿ‘ฑ๐Ÿ“”, ๐Ÿ‡บ๐Ÿ‡ธ๐Ÿ”, ๐Ÿ“‚, โ™’๏ธ.) ๐Ÿ‘† ๐Ÿ”œ โš™๏ธ 1๏ธโƒฃ ๐ŸŽ ๐Ÿ’ง.

๐ŸŒ… โš  ๐Ÿ”‘ ๐Ÿ’ง.

๐Ÿ† ๐Ÿ” ๐Ÿ“Ÿ ๐Ÿ’ง, โœ‹๏ธ ๐ŸŒ– ๐Ÿ— ๐Ÿ› ๏ธ โšซ๏ธ ๐Ÿšš ๐ŸŒ… ๐Ÿ“ถ. โšซ๏ธ ๐ŸŒ… ๐Ÿ—, ๐Ÿ“š ๐Ÿ•โ€๐Ÿฆบ ๐Ÿ”š ๐Ÿ†™ โœ” ๐Ÿ”‘ ๐Ÿ’ง.

Note

โšซ๏ธ โš  ๐Ÿ‘ˆ ๐Ÿ”  ๐Ÿค ๐Ÿ•โ€๐Ÿฆบ ๐Ÿ“› ๐Ÿ‘ซ ๐Ÿ’ง ๐ŸŽ ๐ŸŒŒ, โš’ โšซ๏ธ ๐Ÿ• ๐Ÿ‘ซ ๐Ÿท.

โœ‹๏ธ ๐Ÿ”š, ๐Ÿ‘ซ ๐Ÿ› ๏ธ ๐ŸŽ Oauth2๏ธโƒฃ ๐Ÿฉ.

FastAPI ๐Ÿ”Œ ๐Ÿš™ ๐ŸŒ ๐Ÿ‘ซ Oauth2๏ธโƒฃ ๐Ÿค ๐Ÿ’ง fastapi.security.oauth2.

Security ๐Ÿ‘จโ€๐ŸŽจ dependencies

๐ŸŽ ๐ŸŒŒ ๐Ÿ‘† ๐Ÿ’ช ๐Ÿ”ฌ list Depends ๐Ÿ‘จโ€๐ŸŽจ dependencies ๐Ÿ”ข (๐Ÿ”ฌ ๐Ÿ”— โžก ๐Ÿ› ๏ธ ๐Ÿ‘จโ€๐ŸŽจ), ๐Ÿ‘† ๐Ÿ’ช โš™๏ธ Security โฎ๏ธ scopes ๐Ÿ“ค.