Methods of Authorization in Modern Systems
A technical article on authorization methods, architectural features, practical examples, and security considerations.
Introduction
Authorization is the process of verifying a user's rights to perform certain actions or access resources. It always follows authentication, which answers the question "Who are you?" In contrast, authorization responds to the question "What are you allowed to do?"
There are numerous ways to implement authorization—from classic cookie-based sessions to distributed systems using JWT, OAuth, and multi-factor authentication. The choice of method directly affects:
* User experience (for instance, "remember me" functionality);
* Security (resistance to session hijacking, XSS, CSRF);
* Scalability (support for distributed APIs, microservices, mobile apps).
Let us examine the key approaches.
1. Session-Based Authorization (Cookie-based Sessions)
Workflow
- The user logs in using a username and password.
- The server creates a unique
session_id
(usually a UUID). - The
session_id
is stored in memory (Redis, Memcached, PostgreSQL). - The client receives a cookie
sessionid=…
containing this ID. - With each request, the browser automatically sends the cookie.
- The server verifies the ID in the storage and identifies the user.
Architectural Nuances
- A centralized session storage (e.g., Redis) is necessary for clustered architectures.
- Cookies must be configured with attributes:
HttpOnly
,Secure
,SameSite=Lax
. - It is possible to forcibly terminate sessions (e.g., upon password change).
Example in Django
# settings.py
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
SESSION_COOKIE_AGE = 60 * 30 # 30 minutes
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = "Lax"
Django handles session creation and validation automatically; you only need to use request.session
.
2. Token-Based Authorization
Workflow
- After login, the server issues a token (e.g.,
uuid4
,secrets.token_hex
). - The token is stored on the client (in
localStorage
, mobile app). - The client sends it in a header:
Authorization: Bearer <token>
- The server looks up the token in storage (e.g., a database or Redis) and identifies the user.
Features
- Easy to use in APIs (REST, GraphQL).
- Works well with mobile applications.
- Requires a backend store for token verification.
Example (Flask + Redis)
import secrets, redis
from flask import Flask, request
app = Flask(__name__)
r = redis.Redis()
@app.route("/login", methods=["POST"])
def login():
if request.form["username"] == "admin" and request.form["password"] == "123":
token = secrets.token_hex(16)
r.setex(token, 3600, "admin") # token expires in 1 hour
return {"token": token}
return {"error": "unauthorized"}, 401
@app.route("/protected")
def protected():
token = request.headers.get("Authorization", "").replace("Bearer ", "")
user = r.get(token)
if user:
return {"message": f"Hello, {user.decode()}"}
return {"error": "forbidden"}, 403
3. JWT (JSON Web Token)
What is JWT?
JWT is a JSON object signed with a secret key (symmetric or asymmetric). It consists of three parts:
* Header (token type, signing algorithm)
* Payload (data: user_id
, role
, exp
)
* Signature (cryptographic signature)
Example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJhZG1pbiIsImV4cCI6MTczNjQ0OTIwMH0
.QBzVnQKq2o8u4s_wtX7Ik7drXHqE0w3p8DcAkWxS4mQ
Advantages
- No need to store sessions in a database.
- Scales well for distributed APIs without a shared session store.
- Can embed useful data directly in the token.
Drawbacks
- Tokens cannot be easily revoked without a blacklist.
- If leaked, a token grants full access until expiration (
exp
) is reached.
Example Using PyJWT
import jwt, datetime
SECRET = "super_secret"
def create_jwt(user_id: int):
payload = {
"user_id": user_id,
"role": "admin",
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
}
return jwt.encode(payload, SECRET, algorithm="HS256")
def verify_jwt(token: str):
try:
return jwt.decode(token, SECRET, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
token = create_jwt(42)
print("JWT:", token)
print("Payload:", verify_jwt(token))
4. OAuth 2.0
Purpose
OAuth allows third-party applications to access user data without requiring the user's password.
Flows (Grant Types)
- Authorization Code Flow — standard for web applications.
- Implicit Flow — used for SPAs (now deprecated in favor of PKCE).
- Client Credentials Flow — used for server-to-server interactions.
- Device Flow — intended for TVs and consoles.
Architecture
- The client redirects the user to
/authorize
. - The user authenticates with the provider (e.g., Google, GitHub).
- The provider returns an authorization
code
. - The client exchanges the
code
for anaccess_token
. - The
access_token
is used to access the API.
Example (FastAPI + Authlib + GitHub)
from fastapi import FastAPI, Request
from authlib.integrations.starlette_client import OAuth
app = FastAPI()
oauth = OAuth()
oauth.register(
name="github",
client_id="GITHUB_ID",
client_secret="GITHUB_SECRET",
authorize_url="https://github.com/login/oauth/authorize",
access_token_url="https://github.com/login/oauth/access_token",
api_base_url="https://api.github.com/"
)
@app.get("/login")
async def login(request: Request):
redirect_uri = "http://localhost:8000/auth"
return await oauth.github.authorize_redirect(request, redirect_uri)
@app.get("/auth")
async def auth(request: Request):
token = await oauth.github.authorize_access_token(request)
user = await oauth.github.get("user", token=token)
return {"user": user.json()}
5. Multi-Factor Authorization (MFA / 2FA)
Factors
- Something you know: password, PIN.
- Something you have: phone, token generator.
- Something you are: fingerprint, face.
TOTP (Time-based One-Time Password)
Algorithm based on RFC 6238: code = HMAC(secret, timestamp // 30)
. An authenticator (like Google Authenticator) generates codes every 30 seconds.
Example (using PyOTP)
import pyotp
secret = pyotp.random_base32()
print("Secret:", secret)
totp = pyotp.TOTP(secret)
print("Code:", totp.now()) # one-time code
print("Validation:", totp.verify(totp.now()))
6. Security Best Practices
To ensure that your chosen authorization method is secure, consider:
* HTTPS everywhere — without TLS, any token or cookie can be intercepted.
* HttpOnly cookies — prevent JS from accessing session cookies.
* CSRF protection — mandatory for cookie-based sessions (via tokens or SameSite
).
* XSS protection — proper Content Security Policy (CSP).
* Refresh tokens — short-lived access_token
+ long-lived refresh_token
.
* Key rotation — periodically rotate JWT signing keys.
* Blacklisting — revoke compromised tokens.
* Login notifications — alert users of suspicious logins.
7. Comparison of Approaches
Метод | Где лучше применять | Безопасность | Масштабируемость |
---|---|---|---|
Cookie-сессии | Классические веб-приложения | Средняя | Средняя (Redis) |
Token-based | API, мобильные клиенты | Средняя | Хорошая |
JWT | Микросервисы, REST API | Выше средней | Отличная |
OAuth 2.0 | Интеграция с внешними сервисами | Высокая | Отличная |
MFA / 2FA | Банки, критичные системы | Очень высокая | Доп. уровень |
Conclusions
- Cookie-based sessions are the best choice for classic websites (e.g., Django, Flask).
- Tokens or JWTs are optimal for APIs and mobile applications.
- OAuth 2.0 is the standard for integrations with providers like Google, GitHub, VK, etc.
- MFA is a must-have for financial and personal services.
- Security is not about choosing one method, but about the right combination (e.g., short-lived tokens + HTTPS + key rotation + 2FA).