2026-08-29 13:42:54 +03:00
2026-07-13 09:25:34 +03:00
2026-08-29 13:42:54 +03:00
2026-07-12 22:06:20 +03:00
2026-07-12 22:06:20 +03:00
2026-07-12 22:06:20 +03:00
2026-07-12 22:06:20 +03:00
2026-07-12 22:06:20 +03:00
2026-07-12 22:06:20 +03:00
2026-07-12 22:06:20 +03:00
2026-08-29 13:42:54 +03:00

Auth & Authorization Service

Backend application built with FastAPI + SQLAlchemy, implementing a custom authentication and authorization system without using the framework's built-in auth mechanisms.

Stack

  • FastAPI — web framework, routing, dependency injection
  • SQLAlchemy (ORM) — database access, sessionmaker, declarative models
  • python-jose (jwt) — creating and validating JWT tokens
  • bcrypt / passlib — password hashing
  • Pydantic v2 — request/response validation schemas
  • Alembic — database migrations

Authentication

Authentication is built on JWT tokens (Bearer), without server-side sessions.

  1. The client sends POST /protected/token with email (as username) and password in OAuth2PasswordRequestForm format (form-data).
  2. The server looks up the user by email and verifies the password via bcrypt (hash_check(plain, hashed)).
  3. If the email/password pair is correct — a JWT is issued with payload {"sub": <user.id>}, signed with a secret key (ENV.SECRET_STRING, algorithm ENV.ALGORITHM).
  4. The client passes the token in the Authorization: Bearer <token> header with every subsequent request.
  5. On protected endpoints, the get_current_user dependency:
    • decodes the token (jwt.decode),
    • extracts sub (user id),
    • loads the user from the DB along with their permissions,
    • on any error (invalid/expired token, user not found from the token) — 401 Unauthorized.

Logout in a pure JWT scheme requires either a short token TTL + a refresh token, or a blacklist of revoked tokens (a revoked_tokens table). Alternatively, the token can simply be cleared on the frontend at logout, as the simplest option.

Authorization Model

Instead of the classic roles / business_elements / access_roles_rules scheme (with separate read/create/update/delete × _all), a simpler flat permission model was chosen, which is easier to demonstrate but covers the same principles: a user is granted a set of string permissions, and each endpoint declares which permissions are required to call it.

Database Tables

users

field type description
id int, PK user identifier
name str(64) first name
last_name str(64) last name
middle_name str(64) middle name
email str(255) email, unique
status bool whether the account is active (used instead of is_active for soft delete)
hashed_password str(255) bcrypt password hash

permissions

field type description
id int, PK permission identifier
permission str(64) permission code: admin, can_view, can_create, can_edit, can_delete

user_permission (association table, many-to-many)

field type description
user_id FK → users.id
permission_id FK → permissions.id

A single user can hold several permissions at once (e.g. can_view + can_edit); the admin permission grants full access and allows granting/revoking any permissions, including admin, to/from other users.

Access Check Rules

Every business-logic method (CrudActions) explicitly declares the set of permitted permissions and checks it before performing the action:

Action Required permission
View user (by email/id) can_view or admin
Create user can_create or admin
Update user can_edit or admin
Delete user can_delete or admin

Additionally, a privilege escalation protection rule applies: a user without the admin permission cannot grant the admin permission to themselves or any other user when creating/editing an account.

Access Error Handling

  • 401 Unauthorized — if the request cannot be matched to a valid logged-in user (no token, expired/invalid token, user from the token does not exist).
  • 403 Forbidden — the user is identified, but lacks the required permission for the requested action/resource (including privilege escalation attempts, attempts to claim someone else's email).
  • 404 Not Found — the requested resource (user, permission) does not exist.

API

Method Path Required permission Description
POST /protected/token Login, JWT issuance
POST /protected/logout Login, JWT issuance
GET /protected/get_user_by_email can_view / admin Get user by email
GET /protected/get_user_by_id can_view / admin Get user by id
POST /protected/create_user can_create / admin Create user
PATCH /protected/update_user can_edit / admin Update own profile, or (for admin/can_edit) someone else's via target_id
DELETE /protected/delete_user can_delete / admin Delete (deactivate) user

Test Data

For demonstrating the system, the DB must contain at least: Passwords d@d.d, d1@d.d, d2@d.d 12345678

  • 1 user with the admin permission (full access)
  • 1 user with the can_view permission (view only)
  • 1 user with no permissions (for testing 403)

Project Structure

src/
  db/                # ActionsDB — DB access (SQLAlchemy sessions, queries)
  model/
    database_model/  # SQLAlchemy models (User, Permissions)
    user/             # Pydantic schemas (UserOut, UserCreate, UserUpdate, PermissionIn/Out)
    env_read/         # Reading env via pydantic settings
  service/
    crud_actions/     # CrudActions — business logic + access permission checks
    JWT/               # JWT (token encoding/decoding), Hash (bcrypt)
    auth/              # JWT token creation and verification
  errors/              # centralized HTTP errors (401/403/404)
  web/                 # FastAPI routers (endpoints)
  migrations/          # DB migrations
  tests/               # tests (empty, not yet created)

Running

poetry
make run

Rename env

S
Description
auth-project
Readme
146 KiB
Languages
Python 97.6%
Mako 2.2%
Makefile 0.2%