Autoryzacja i JWT w FastAPI

Kacper Sieradziński
Kacper Sieradziński
12 maja 2025Edukacja4 min czytania

FastAPI to framework, który powstał z myślą o szybkim i bezpiecznym budowaniu API. Jednym z najczęstszych wymagań w nowoczesnych aplikacjach jest autoryzacja użytkowników — czyli upewnienie się, że tylko zalogowani i uprawnieni użytkownicy mogą wykonywać określone akcje.

Obraz główny Autoryzacja i JWT w FastAPI

Standardem w tym zakresie są JWT (JSON Web Tokens) — lekkie, bezstanowe tokeny autoryzacyjne, które pozwalają skalować aplikację bez konieczności trzymania sesji po stronie serwera. W tym przewodniku poznasz, jak zaimplementować kompleksowy system autoryzacji w FastAPI z użyciem JWT, refresh tokenów i OAuth2.

Jak działa JWT (JSON Web Token)

JWT składa się z trzech części oddzielonych kropkami:

Bash
1 header.payload.signature
  • Header – określa algorytm szyfrowania (np. HS256, RS256) i typ tokena.
  • Payload – zawiera dane o użytkowniku (user_id, role, exp, iat) i inne claimy.
  • Signature – zapewnia integralność tokena, gwarantując, że nie został zmodyfikowany.

Po zalogowaniu serwer zwraca token, który klient wysyła w nagłówku:

HTTP
1 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJhZG1pbiJ9.signature

Serwer nie trzyma żadnego stanu — wystarczy poprawny podpis, by zweryfikować autoryzację. To właśnie czyni JWT idealnym rozwiązaniem dla skalowalnych aplikacji i mikroserwisów.

Zalety JWT w FastAPI

  • Bezstanowość — serwer nie musi przechowywać sesji w pamięci lub bazie danych.
  • Skalowalność — token zawiera wszystkie informacje potrzebne do autoryzacji.
  • Wydajność — weryfikacja tokena jest szybka (sprawdzenie podpisu).
  • Przenośność — działa z różnymi platformami i językami.

Konfiguracja JWT w FastAPI

Najpierw zainstaluj wymagane biblioteki:

Bash
1 pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt] python-multipart

Następnie zdefiniuj podstawowy system logowania i generowania tokenów:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from fastapi import FastAPI, Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jose import JWTError, jwt from datetime import datetime, timedelta from passlib.context import CryptContext from typing import Optional app = FastAPI() # Konfiguracja SECRET_KEY = "your-secret-key-change-in-production"# W produkcji użyj zmiennych środowiskowych ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 # Kontekst do hashowania haseł pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # OAuth2 scheme oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # Tymczasowa baza danych użytkowników fake_users_db = { "kacper": { "username": "kacper", "email": "kacper@example.com", "hashed_password": pwd_context.hash("1234"), "role": "admin" } } def verify_password(plain_password: str, hashed_password: str) -> bool: """Weryfikacja hasła""" return pwd_context.verify(plain_password, hashed_password) def get_password_hash(password: str) -> str: """Hashowanie hasła""" return pwd_context.hash(password) def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: """Tworzenie tokena JWT""" to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({"exp": expire, "iat": datetime.utcnow()}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt

Logowanie i generowanie tokenów

Dodaj endpoint do logowania użytkownika i generowania tokenu:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @app.post("/token") async def login(form_data: OAuth2PasswordRequestForm = Depends()): """Endpoint logowania — zwraca access token""" user = fake_users_db.get(form_data.username) if not user or not verify_password(form_data.password, user["hashed_password"]): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user["username"], "role": user.get("role", "user")}, expires_delta=access_token_expires ) return { "access_token": access_token, "token_type": "bearer", "expires_in": ACCESS_TOKEN_EXPIRE_MINUTES * 60 }

Teraz możesz zalogować się z poziomu /docs i automatycznie wstrzyknąć token do żądań. FastAPI automatycznie obsługuje formularz OAuth2 w Swagger UI.

Pełna implementacja z bazą danych

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from sqlalchemy.orm import Session def authenticate_user(db: Session, username: str, password: str): """Weryfikacja użytkownika w bazie danych""" user = db.query(User).filter(User.username == username).first() if not user: return False if not verify_password(password, user.hashed_password): return False return user @app.post("/token") async def login( form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) ): user = authenticate_user(db, form_data.username, form_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user.username, "user_id": user.id, "role": user.role}, expires_delta=access_token_expires ) return {"access_token": access_token, "token_type": "bearer"}

Ochrona endpointów – Dependency Injection

FastAPI oferuje mechanizm Dependency Injection, który umożliwia wstrzykiwanie logiki autoryzacyjnej do endpointów.

Podstawowa weryfikacja tokena

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from fastapi import Depends, HTTPException, status from jose import JWTError, jwt def get_current_user(token: str = Depends(oauth2_scheme)) -> dict: """Dependency do pobrania aktualnego użytkownika z tokena JWT""" credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise credentials_exception except JWTError: raise credentials_exception user = fake_users_db.get(username) if user is None: raise credentials_exception return user @app.get("/users/me") async def read_users_me(current_user: dict = Depends(get_current_user)): """Chroniony endpoint — wymaga autoryzacji""" return { "username": current_user["username"], "email": current_user["email"], "role": current_user.get("role") }

Każdy endpoint zabezpieczony przez Depends(get_current_user) wymaga poprawnego tokena w nagłówku Authorization: Bearer <token>.

Pobieranie użytkownika z bazy danych

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def get_current_user( token: str = Depends(oauth2_scheme), db: Session = Depends(get_db) ): """Pobierz użytkownika z bazy danych na podstawie tokena""" credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") user_id: int = payload.get("user_id") if username is None: raise credentials_exception except JWTError: raise credentials_exception user = db.query(User).filter(User.id == user_id).first() if user is None: raise credentials_exception return user

Sprawdzanie ról i uprawnień

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from fastapi import Depends, HTTPException from typing import List def require_role(allowed_roles: List[str]): """Dependency factory do sprawdzania ról""" def role_checker(current_user: dict = Depends(get_current_user)): user_role = current_user.get("role") if user_role not in allowed_roles: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions" ) return current_user return role_checker @app.delete("/users/{user_id}") async def delete_user( user_id: int, current_user: dict = Depends(require_role(["admin"])) ): """Tylko admin może usuwać użytkowników""" # Logika usuwania return {"message": f"User {user_id} deleted"}

Prostsza wersja z dekoratorem

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from functools import wraps def admin_required(func): """Dekorator do sprawdzania uprawnień admina""" @wraps(func) async def wrapper(*args, current_user: dict = Depends(get_current_user), **kwargs): if current_user.get("role") != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required" ) return await func(*args, current_user=current_user, **kwargs) return wrapper @app.delete("/users/{user_id}") @admin_required async def delete_user(user_id: int, current_user: dict): return {"message": f"User {user_id} deleted"}

Refresh tokeny

Access token ma krótki czas życia (np. 15–30 minut). Aby użytkownik nie musiał logować się ponownie, stosuje się refresh tokeny.

Strategia Access Token + Refresh Token

  • Access token – krótkotrwały (15–30 minut), używany do autoryzacji żądań.
  • Refresh token – długotrwały (7–30 dni), służy tylko do odświeżenia access tokena.

Implementacja z bazą danych

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 from sqlalchemy import Column, Integer, String, DateTime from sqlalchemy.ext.declarative import declarative_base from datetime import datetime, timedelta import secrets Base = declarative_base() class RefreshToken(Base): __tablename__ = "refresh_tokens" id = Column(Integer, primary_key=True) token = Column(String, unique=True, index=True) user_id = Column(Integer) expires_at = Column(DateTime) created_at = Column(DateTime, default=datetime.utcnow) def create_refresh_token(user_id: int, db: Session) -> str: """Utwórz refresh token i zapisz w bazie danych""" token = secrets.token_urlsafe(32) expires_at = datetime.utcnow() + timedelta(days=7) db_token = RefreshToken( token=token, user_id=user_id, expires_at=expires_at ) db.add(db_token) db.commit() return token @app.post("/token") async def login( form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) ): user = authenticate_user(db, form_data.username, form_data.password) if not user: raise HTTPException(status_code=401, detail="Invalid credentials") access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user.username, "user_id": user.id}, expires_delta=access_token_expires ) refresh_token = create_refresh_token(user.id, db) return { "access_token": access_token, "refresh_token": refresh_token, "token_type": "bearer" } @app.post("/refresh") async def refresh_token( refresh_token: str, db: Session = Depends(get_db) ): """Odśwież access token używając refresh tokena""" db_token = db.query(RefreshToken).filter( RefreshToken.token == refresh_token, RefreshToken.expires_at > datetime.utcnow() ).first() if not db_token: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired refresh token" ) # Generuj nowy access token user = db.query(User).filter(User.id == db_token.user_id).first() access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user.username, "user_id": user.id}, expires_delta=access_token_expires ) return {"access_token": access_token, "token_type": "bearer"} @app.post("/logout") async def logout(refresh_token: str, db: Session = Depends(get_db)): """Unieważnij refresh token (wylogowanie)""" db_token = db.query(RefreshToken).filter( RefreshToken.token == refresh_token ).first() if db_token: db.delete(db_token) db.commit() return {"message": "Logged out successfully"}

Refresh tokeny najlepiej przechowywać po stronie klienta w HTTP-only cookie (niewidoczne dla JavaScript, odporne na XSS).

Bezpieczne przechowywanie refresh tokenów w cookies

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from fastapi import Response @app.post("/token") async def login( response: Response, form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) ): user = authenticate_user(db, form_data.username, form_data.password) if not user: raise HTTPException(status_code=401, detail="Invalid credentials") access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user.username, "user_id": user.id}, expires_delta=access_token_expires ) refresh_token = create_refresh_token(user.id, db) # Ustaw refresh token w HTTP-only cookie response.set_cookie( key="refresh_token", value=refresh_token, httponly=True, secure=True,# Tylko HTTPS samesite="lax", max_age=7 * 24 * 60 * 60# 7 dni ) return {"access_token": access_token, "token_type": "bearer"}

Integracja z OAuth2

FastAPI ma natywne wsparcie dla OAuth2 — standardu wykorzystywanego przez serwisy takie jak Google, GitHub czy Facebook. Dzięki temu można wdrożyć logowanie przez zewnętrznych dostawców.

Instalacja Authlib

Bash
1 pip install Authlib

Przykład z GitHub OAuth

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 from authlib.integrations.starlette_client import OAuth from starlette.config import Config from starlette.middleware.sessions import SessionMiddleware app.add_middleware(SessionMiddleware, secret_key="your-secret-key") config = Config('.env') oauth = OAuth(config) oauth.register( name="github", client_id="YOUR_GITHUB_CLIENT_ID", client_secret="YOUR_GITHUB_CLIENT_SECRET", server_metadata_url="https://token.actions.githubusercontent.com/.well-known/openid-configuration", client_kwargs={ "scope": "openid email profile" } ) @app.get("/login/github") async def login_via_github(request: Request): """Przekieruj użytkownika do GitHub OAuth""" redirect_uri = request.url_for("auth_github") return await oauth.github.authorize_redirect(request, redirect_uri) @app.get("/auth/github") async def auth_github(request: Request, db: Session = Depends(get_db)): """Callback z GitHub OAuth""" token = await oauth.github.authorize_access_token(request) # Pobierz dane użytkownika z GitHub async with httpx.AsyncClient() as client: user_info_response = await client.get( "https://api.github.com/user", headers={"Authorization": f"Bearer {token['access_token']}"} ) user_info = user_info_response.json() # Znajdź lub utwórz użytkownika w bazie user = db.query(User).filter(User.email == user_info.get("email")).first() if not user: user = User( username=user_info["login"], email=user_info.get("email"), provider="github" ) db.add(user) db.commit() # Utwórz własny JWT token access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user.username, "user_id": user.id}, expires_delta=access_token_expires ) return {"access_token": access_token, "token_type": "bearer"}

Przykład z Google OAuth

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 oauth.register( name="google", server_metadata_url="https://accounts.google.com/.well-known/openid-configuration", client_kwargs={ "scope": "openid email profile" } ) @app.get("/login/google") async def login_via_google(request: Request): redirect_uri = request.url_for("auth_google") return await oauth.google.authorize_redirect(request, redirect_uri) @app.get("/auth/google") async def auth_google(request: Request, db: Session = Depends(get_db)): token = await oauth.google.authorize_access_token(request) user_info = token.get("userinfo") if not user_info: raise HTTPException(status_code=400, detail="Failed to get user info") # Znajdź lub utwórz użytkownika user = db.query(User).filter(User.email == user_info["email"]).first() if not user: user = User( username=user_info["name"], email=user_info["email"], provider="google" ) db.add(user) db.commit() # Utwórz JWT token access_token = create_access_token( data={"sub": user.username, "user_id": user.id}, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) ) return {"access_token": access_token, "token_type": "bearer"}

OAuth2 w połączeniu z JWT daje pełny system autoryzacji zewnętrznej i wewnętrznej.

Przykład pełnego flow autoryzacji

Pełny proces autoryzacji w aplikacji FastAPI wygląda następująco:

  1. Użytkownik loguje się przez /token (lub /login/github dla OAuth2).
  2. Serwer generuje access_tokenrefresh_token.
  3. Klient przesyła token w nagłówkach przy każdym żądaniu:
HTTP
1 Authorization: Bearer <access_token>
  1. Po wygaśnięciu tokena — klient odświeża go przez /refresh używając refresh tokena.
  2. Endpointy weryfikują token przy pomocy Depends(get_current_user).
  3. Po wylogowaniu — refresh token jest unieważniany w bazie danych.

Pełny przykład aplikacji

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 from fastapi import FastAPI, Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from sqlalchemy.orm import Session from jose import JWTError, jwt from datetime import datetime, timedelta from passlib.context import CryptContext app = FastAPI(title="Secure API", version="1.0.0") # ... konfiguracja jak wcześniej ... @app.post("/token") async def login( form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) ): user = authenticate_user(db, form_data.username, form_data.password) if not user: raise HTTPException(status_code=401, detail="Invalid credentials") access_token = create_access_token( data={"sub": user.username, "user_id": user.id}, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) ) refresh_token = create_refresh_token(user.id, db) return { "access_token": access_token, "refresh_token": refresh_token, "token_type": "bearer" } @app.get("/users/me") async def read_users_me(current_user: User = Depends(get_current_user)): return current_user @app.post("/users", status_code=201) async def create_user( user_data: UserCreate, current_user: User = Depends(require_role(["admin"])), db: Session = Depends(get_db) ): # Tylko admin może tworzyć użytkowników return create_user_in_db(user_data, db)

Dobre praktyki bezpieczeństwa

Przy implementowaniu autoryzacji JWT w FastAPI warto przestrzegać następujących zasad:

Używaj HTTPS

Nigdy nie przesyłaj tokenów po HTTP — są narażone na przechwycenie. W produkcji zawsze wymuś HTTPS.

Krótki czas życia access tokena

Access tokeny powinny wygasać szybko (15–30 minut), aby zminimalizować szkody w przypadku przejęcia.

Refresh tokeny tylko w cookies HTTP-only

Używaj HTTP-only cookies dla refresh tokenów — są chronione przed JavaScriptem i atakami XSS.

Python
1 2 3 4 5 6 7 response.set_cookie( key="refresh_token", value=refresh_token, httponly=True, secure=True,# Tylko HTTPS samesite="strict" )

Wylogowanie = unieważnienie tokena

Po wylogowaniu użytkownika unieważnij refresh token w bazie danych (usuń go lub oznacz jako unieważniony).

Stosuj silne klucze tajne i algorytmy asymetryczne (RS256)

W produkcji użyj RS256 zamiast HS256 dla większego bezpieczeństwa:

Python
1 2 3 4 5 6 7 8 9 10 11 12 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa # Generowanie kluczy private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) public_key = private_key.public_key() # Podpisywanie token = jwt.encode(payload, private_key, algorithm="RS256") # Weryfikacja payload = jwt.decode(token, public_key, algorithms=["RS256"])

Waliduj datę ważności (exp) i issuer (iss)

Zawsze sprawdzaj expiss (issuer) w tokenie:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def verify_token(token: str): try: payload = jwt.decode( token, SECRET_KEY, algorithms=[ALGORITHM], options={"verify_signature": True, "verify_exp": True}, issuer="your-app",# Weryfikuj issuer audience="your-api"# Weryfikuj audience ) return payload except jwt.ExpiredSignatureError: raise HTTPException(status_code=401, detail="Token expired") except jwt.InvalidTokenError: raise HTTPException(status_code=401, detail="Invalid token")

Loguj nieautoryzowane próby dostępu

Monitoruj i loguj próby użycia nieprawidłowych tokenów:

Python
1 2 3 4 5 6 7 8 9 10 11 import logging logger = logging.getLogger(__name__) def get_current_user(token: str = Depends(oauth2_scheme)): try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) # ... except JWTError as e: logger.warning(f"Invalid token attempt: {str(e)}") raise HTTPException(status_code=401, detail="Invalid token")

Rate limiting dla endpointów logowania

Ogranicz liczbę prób logowania, aby chronić przed brute force:

Python
1 2 3 4 5 6 7 8 9 from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) @app.post("/token") @limiter.limit("5/minute") async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()): # ...

Używaj zmiennych środowiskowych dla kluczy

Nigdy nie trzymaj kluczy w kodzie:

Python
1 2 3 4 5 import os from decouple import config SECRET_KEY = config("SECRET_KEY") ALGORITHM = config("ALGORITHM", default="HS256")

Waliduj dane wejściowe

Zawsze waliduj dane logowania i tokeny przed przetworzeniem — FastAPI robi to automatycznie dzięki Pydantic.

Podsumowanie

FastAPI w połączeniu z JWT i OAuth2 daje kompletny system autoryzacji, który jest bezpieczny, skalowalny i łatwy w integracji z frontendem. Dzięki typowaniu, walidacji i dependency injection FastAPI pozwala pisać czysty, nowoczesny kod backendowy, bez zbędnego boilerplate'u.

Najważniejsze zalety:

  • Dependency Injection — elegancka ochrona endpointów
  • Automatyczna walidacja — dzięki Pydantic
  • Automatyczna dokumentacja — Swagger UI z integracją OAuth2
  • Skalowalność — bezstanowe tokeny JWT
  • Bezpieczeństwo — wbudowane mechanizmy OAuth2 i JWT

Jeśli szukasz frameworka do budowy bezpiecznego API z autoryzacją — FastAPI z JWT to doskonały wybór. Zacznij od prostego logowania i stopniowo dodawaj refresh tokeny, OAuth2 i zaawansowane funkcjonalności zgodnie z potrzebami projektu.