ENV File Parameters
Put it next to the app
EXTERNAL_FASTAPI_PORT = 80
INTERNAL_FASTAPI_PORT = 8000
BASE_URL = https://auth-central-challange.vercel.app
ACCESS_TOKEN_LIFETIME = 2
REFRESH_TOKEN_LIFETIME = 4
AUTH_DB_STACK = redis # sqlite, redis
REDIS_HOST = redis
REDIS_PORT = 6379
REDIS_PASSWORD = redispassword
To Run
fastapi dev app/src/main.py
In this layer, the application infrastructure is defined, such as:
-
Authentication utilities such as token creation, management, and validation
-
Database client and its models (tables)
-
Errors related to this layer and other layers
- include status code and message
-
Services for interacting with external APIs
- include interfaces and their implementation
-
Fastapi config such as
- middleware
- tasks that should be run on startup or shutdown, such as create and close database client
- implement some states based on settings loaded from .env in main app, to have access them throughout the entire project
-
Mixin classes
-
Application settings load from the
.env
file- load with pydantic_settings
infra/
│
├── auth/
│ └── <files or directories...>
│
├── db/
│ ├── redis/
│ │ └── <files or directories...>
│ │
│ ├── mongodb/
│ │ └── <files or directories...>
│ │
│ └── sqlite/
│ └── <files or directories...>
│
├── exceptions/
│ └── <files...>
│
├── external_api/
│ ├── interface/
│ │ └── <files...>
│ │
│ └── service/
│ └── <files...>
│
├── fastapi_config/
│ └── <files...>
│
├── mixins/
│ └── <files...>
│
└── settings/
└── <files...>
In this layer, data models are defined that are only used inside the application, meaning between layers, for transferring data.
domain/
├── mock_data/
│ └── <files...>
│
└── schemas/
└── <schema_group_name>/
└── <files...>
In this layer, data models are defined that are only used for receiving or sending data to the client.
models/
├── filter/
│ └── <files...>
│
└── schemas/
└── <schema_group_name>/
└── <files...>
In this layer, communication with the database is handled. Repository classes are defined here, whose methods provide interaction with the database. Each repository class inherits from an interface defined in this layer. Interfaces define the structure of database communication, so we can have multiple repository classes based on a single interface and use them for dependency injection.
repo/
├── interface/
│ └── <files...>
│
└── <implementation_name>/
└── <files...>
Naming implementations can be based on:
- Storage type — for example:
sql
,nosql
repo/
├── interface/
│ └── <files...>
│
├── sql/
│ └── <files...>
│
└── nosql/
└── <files...>
- Storage name — for example:
postgresql
, ormongodb
.
repo/
├── interface/
│ └── <files...>
│
├── postgresql/
│ └── <files...>
│
└── mongodb/
└── <files...>
In this layer, endpoints are defined along with their dependencies, response statuses, and other endpoint-related configurations.
routes/
├── api_endpoints/
│ ├── <endpoint_group_name>/
│ │ └── <files...>
│ │
│ ├── <endpoint_group_name>/
│ │ └── <files...>
│ │
│ └── main_router.py
│
├── depends/
│ └── <files...>
│
└── http_response/
└── <files...>
In this layer, the application’s business logic is defined. This layer acts as an important bridge between endpoints in the Routes layer, the database in the Repo layer, and external APIs in the Infra layer.
usecase/
├── <usecase_group_name>/
│ └── <files...>
│
└── <usecase_group_name>/
└── <files...>
The layers are not limited to the mentioned items and can also include other related configurations.
After user registration, when logging in, the access token, refresh token, and token type are received from the auth server.
These information, along with the user’s email and the calculated expiration time
of the access token and refresh token, are stored in the database.
When saving this information in the database, a UUID is generated as the access identifier and stored with the information in database as device_id
.
The email is stored to let us delete all auth records at once if the user changes it later. The device_id is stored to allow multi-device login, manage tokens per device, show the list of logged-in devices, let the user log out from any device, and notify them of new logins to handle unauthorized access if needed.
In Redis, the device_id
is used as the name
, and the other details are stored as key–value pairs.
Example (Redis schema):
UUID: <uuid>
│
├── email: 'sample@gmail.com'
│
├── token_type: 'bearer'
│
├── access_token: 'eyJhbGciOiJI...'
│
├── access_expiry: 2025-09-06 19:01:45.165131+00:00
│
├── refresh_token: 'eyJhbGciOiJI...'
│
└── refresh_expiry: 2025-09-06 19:03:45.165131+00:00
In the SQL database, the device_id
is stored along with the rest of the information.
Example (SQL schema):
| id | device_id (UUID) | email | access_token | access_expiry | refresh_token | refresh_expiry | token_type |
| 1 | e8fbab2370f64befb83f2ec62ffc6055 | sample@gmail.com | eyJhbGciOiJI... | 2025-09-06 19:01:45.165131+00:00 | eyJhbGciOiJI... | 2025-09-06 19:03:45.165131+00:00 | bearer |
Immediately after saving the information in the database, the device_id
is stored in the user’s cookies as a session-type cookie.
For every process that requires user authentication:
-
Get the
device_id
from the cookie. -
Use it to fetch the information from the database.
-
Check the access token expiration date:
-
If the access token is still valid → the user request is continues using the access token.
-
If the access token is invalid → check the refresh token expiration date.
-
If the refresh token is expired → stop the request here, return an error message, or redirect the user to the login page.
-
If the refresh token is valid → send it to
auth-server/refresh-token
endpoint, get new access and refresh tokens, update the database using thedevice_id
, and continue the user’s request with the new access token.
-
-
To optimize further, we can set an expiry time for records in Redis,it can be equal to the lifetime of the refresh token. After the time has passed (or the refresh token has expired), the record will automatically be removed, and the user will need to log in again. When the tokens expire, its database record will no longer have any use.