Skip to main content

Authentication and authorization

TaskForge API uses a layered approach to protect every endpoint.

bcrypt password hashing

User passwords are hashed with bcrypt before storage. Plain-text passwords are never persisted.

JWT tokens

Access tokens expire after 1 hour (JWT_ACCESS_TOKEN_EXPIRES=3600). Refresh tokens expire after 30 days (JWT_REFRESH_TOKEN_EXPIRES=2592000). Both values are configurable via environment variables.

Refresh token revocation

Refresh tokens are stored in the refresh_tokens database table. Logging out revokes the current token. Changing a password revokes all tokens for the user.

RBAC

Two roles: admin and user. Role-specific middleware (app/middleware/rbac.py) restricts access to admin-only endpoints such as user management.

JWT token flow

Tokens are transmitted in the Authorization header using the Bearer scheme:
Authorization: Bearer <access_token>
The token identity claim is stored under sub. When an access token expires, use the refresh token to obtain a new one:
POST /api/auth/refresh
Authorization: Bearer <refresh_token>

Rate limiting

Flask-Limiter applies rate limits to all endpoints. The defaults, configurable via RATELIMIT_DEFAULT in .env, are:
  • 200 requests per day
  • 50 requests per hour
Limiting is always enabled in ProductionConfig and always disabled in TestingConfig. Rate limit counters are stored in memory by default (RATELIMIT_STORAGE_URL=memory://). In a multi-worker deployment, use a Redis storage URL to share counters across workers:
.env
RATELIMIT_STORAGE_URL=redis://localhost:6379
Response headers include the current limit status:
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets

Input validation

All request payloads are validated by functions in app/utils/validators.py before reaching service or model code. Validation covers:
  • Required field presence
  • String length and format (email addresses, usernames, passwords)
  • Enum membership (task status and priority values)
  • Date format correctness
  • Numeric range (pagination parameters)
SQLAlchemy’s ORM parameterizes all database queries, preventing SQL injection regardless of input content.

CORS configuration

Cross-origin requests are restricted to the origins listed in CORS_ORIGINS. Configure this variable to match your frontend’s origin:
.env
CORS_ORIGINS=https://your-frontend.example.com
The allowed methods are GET, POST, PUT, PATCH, DELETE, and OPTIONS. The allowed headers are Content-Type and Authorization. All configuration is applied in app/config.py:52-54.
The default CORS_ORIGINS value allows localhost:3000 and localhost:5173. Update this to your production frontend origin before deploying.

HTTPS

HTTPS is enforced by Azure App Service in the production environment. The platform terminates TLS and forwards requests to the application over HTTP internally. No additional Flask configuration is required for TLS termination.

Refresh token security

Refresh tokens are stored in the refresh_tokens table with an expires_at timestamp. The application enforces the following:
ActionEffect
POST /api/auth/logoutRevokes the current refresh token only
PUT /api/auth/change-passwordRevokes all refresh tokens for the authenticated user
Token expiryExpired tokens are rejected even if not explicitly revoked
This design allows users to stay logged in across multiple devices while being able to terminate all sessions by changing their password.

Environment security

SECRET_KEY and JWT_SECRET_KEY must be cryptographically random values. The placeholders in .env.example must never be used in production. Generate secure values:
python -c "import secrets; print(secrets.token_hex(32))"
Run this command twice and assign the outputs to SECRET_KEY and JWT_SECRET_KEY respectively.
Never commit .env to version control. The repository’s .gitignore excludes it, but verify this before pushing.

SonarCloud analysis

The code-quality.yml workflow runs SonarCloud on every push and pull request to main, master, and develop. It analyzes:
  • Bugs — code that is likely incorrect
  • Vulnerabilities — security weaknesses (OWASP categories)
  • Code smells — maintainability issues
  • Coverage — percentage of lines exercised by tests
  • Duplications — copy-pasted code blocks
The workflow fails the SonarCloud step (but does not block deployment) if the Quality Gate is not passed. Results are visible on the SonarCloud dashboard.

Security checklist

  • SECRET_KEY is a cryptographically random value (at least 32 bytes)
  • JWT_SECRET_KEY is a cryptographically random value (at least 32 bytes), different from SECRET_KEY
  • .env is listed in .gitignore and has never been committed
  • Azure App Service application settings are used instead of a .env file in production
  • AZURE_SQL_PASSWORD uses a strong password meeting Azure SQL complexity requirements
  • FLASK_ENV is set to production
  • DEBUG is False (enforced by ProductionConfig)
  • TESTING is False (enforced by ProductionConfig)
  • All four AZURE_SQL_* variables are set so the application does not fall back to SQLite
  • The Azure SQL firewall allows only the App Service outbound IP addresses
  • scripts/init_db_azure.sql was used to initialize the schema (not db.create_all() alone)
  • HTTPS is enforced on the Azure App Service (HTTPS Only setting enabled)
  • CORS_ORIGINS is restricted to your production frontend origin, not localhost
  • RATELIMIT_ENABLED is true
  • RATELIMIT_STORAGE_URL points to a shared Redis instance if running multiple Gunicorn workers
  • JWT_ACCESS_TOKEN_EXPIRES is set to an appropriate short lifetime (default 3600 seconds)
  • JWT_REFRESH_TOKEN_EXPIRES is appropriate for your session requirements (default 2592000 seconds)
  • Users are informed to log out when using shared devices (revokes the active refresh token)
  • SonarCloud Quality Gate is passing for the current codebase
  • No open vulnerabilities or security hotspots in the SonarCloud dashboard
  • Test coverage is above 70% (enforced by the deployment pipeline)

Build docs developers (and LLMs) love