Skip to main content
TaskForge API uses JSON Web Tokens (JWT) for authentication. Every protected endpoint requires a valid access token in the Authorization header. Refresh tokens allow you to obtain new access tokens without re-entering credentials.
Never expose your access token or refresh token in client-side code, logs, or version control. Treat them with the same care as passwords.

Token overview

TokenLifetimePurpose
Access token1 hour (configurable via JWT_ACCESS_TOKEN_EXPIRES)Authenticate requests to protected endpoints
Refresh token30 days (configurable via JWT_REFRESH_TOKEN_EXPIRES)Obtain new access tokens; stored and tracked in the database
The typical authentication flow is:
  1. Register or log in to receive an access token and a refresh token.
  2. Include the access token in the Authorization: Bearer <token> header on every request.
  3. When the access token expires, call POST /api/auth/refresh with the refresh token to get a new access token.
  4. Call POST /api/auth/logout to revoke the refresh token and end the session.

Registration

POST /api/auth/register
Creates a new user account with the user role. Rate limited to 5 requests per hour.

Request body

username
string
required
Unique username. Must be 3–80 characters and contain only letters, numbers, underscores (_), and hyphens (-).
email
string
required
Unique email address. Must be a valid email format, 3–120 characters, with no consecutive dots.
password
string
required
Account password. Must be 8–128 characters and contain at least one uppercase letter, one lowercase letter, and one number.
first_name
string
User’s first name. Optional.
last_name
string
User’s last name. Optional.

Example

curl -X POST http://localhost:5000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "username": "johndoe",
    "email": "[email protected]",
    "password": "Password123!",
    "first_name": "John",
    "last_name": "Doe"
  }'
{
  "success": true,
  "message": "Usuario registrado con exito",
  "data": {
    "id": 1,
    "username": "johndoe",
    "email": "[email protected]",
    "first_name": "John",
    "last_name": "Doe",
    "full_name": "John Doe",
    "is_active": true,
    "role": {
      "id": 2,
      "name": "user",
      "description": null
    },
    "created_at": "2026-03-17T12:00:00",
    "updated_at": "2026-03-17T12:00:00"
  }
}

Password rules

Passwords are validated by app/utils/validators.py and must meet all of the following:
  • Minimum 8 characters, maximum 128 characters
  • At least one uppercase letter (A–Z)
  • At least one lowercase letter (a–z)
  • At least one digit (0–9)

Login

POST /api/auth/login
Authenticates a user and returns a JWT access token, a refresh token, and the user object. Rate limited to 10 requests per hour.

Request body

email
string
required
The registered email address.
password
string
required
The account password.

Example

curl -X POST http://localhost:5000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "password": "Password123!"}'
{
  "success": true,
  "message": "Inicio de sesion exitoso",
  "data": {
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "user": {
      "id": 1,
      "username": "johndoe",
      "email": "[email protected]",
      "first_name": "John",
      "last_name": "Doe",
      "full_name": "John Doe",
      "is_active": true,
      "role": {
        "id": 2,
        "name": "user",
        "description": null
      },
      "created_at": "2026-03-17T12:00:00",
      "updated_at": "2026-03-17T12:00:00"
    }
  }
}
The refresh token is stored in the database so it can be individually revoked on logout or password change.

Using access tokens

Include the access token in the Authorization header of every request to a protected endpoint:
curl -X GET http://localhost:5000/api/tasks \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
The server validates the token on each request using the current_user_required middleware decorator (app/middleware/auth.py). If the token is missing, expired, or invalid, the server responds with 401 Unauthorized.

Refreshing tokens

POST /api/auth/refresh
Exchanges a valid refresh token for a new access token. Pass the refresh token in the Authorization header.
curl -X POST http://localhost:5000/api/auth/refresh \
  -H "Authorization: Bearer <refresh_token>"
{
  "success": true,
  "message": "Token refrescado con exito",
  "data": {
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}
If the refresh token is expired or has been revoked, the server returns 401 Unauthorized.

Logout

POST /api/auth/logout
Requires a valid access token in the Authorization header. Revokes the given refresh token in the database, invalidating that session. The access token itself continues to be accepted until it naturally expires.

Request body

refresh_token
string
required
The refresh token to revoke.
curl -X POST http://localhost:5000/api/auth/logout \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "<refresh_token>"}'
{
  "success": true,
  "message": "Sesion cerrada con exito"
}

Get current user

GET /api/auth/me
Returns the profile of the currently authenticated user.
curl -X GET http://localhost:5000/api/auth/me \
  -H "Authorization: Bearer <access_token>"
{
  "success": true,
  "data": {
    "id": 1,
    "username": "johndoe",
    "email": "[email protected]",
    "first_name": "John",
    "last_name": "Doe",
    "full_name": "John Doe",
    "is_active": true,
    "role": {
      "id": 2,
      "name": "user",
      "description": null
    },
    "created_at": "2026-03-17T12:00:00",
    "updated_at": "2026-03-17T12:00:00"
  }
}

Change password

POST /api/auth/change-password
Updates the authenticated user’s password. Rate limited to 3 requests per hour. On success, all existing refresh tokens for the user are automatically revoked, requiring re-login on all devices.

Request body

old_password
string
required
The user’s current password.
new_password
string
required
The new password. Must satisfy the same rules as registration: 8–128 characters, at least one uppercase letter, one lowercase letter, and one digit.
curl -X POST http://localhost:5000/api/auth/change-password \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"old_password": "Password123!", "new_password": "NewPassword456!"}'
{
  "success": true,
  "message": "Contrasena cambiada con exito"
}

Role-based access control

Every user is assigned a role at registration. Two roles exist:
RoleValueDescription
AdminadminFull access to all users’ tasks and user management endpoints
UseruserAccess limited to their own tasks and profile
New accounts always receive the user role. Admin accounts must be created directly in the database or by an existing admin. The RBAC decorators in app/middleware/rbac.py enforce these rules:
  • @admin_required — endpoint accessible only to users with role admin. Returns 403 Forbidden for any other role.
  • @role_required(RoleType.ADMIN, RoleType.USER) — endpoint accessible to any of the specified roles.
  • @resource_owner_or_admin — endpoint accessible to the resource owner or any admin.
Endpoints that require the admin role include user management routes (GET /api/users, DELETE /api/users/<id>, etc.). Standard task endpoints use @resource_owner_or_admin, so admins can see and manage all tasks while regular users can only manage their own.

Rate limits on auth endpoints

EndpointLimit
POST /api/auth/register5 per hour
POST /api/auth/login10 per hour
POST /api/auth/change-password3 per hour
All other endpoints inherit the global default: 200 requests per day / 50 requests per hour. Rate limiting is controlled by Flask-Limiter and can be disabled or reconfigured via the RATELIMIT_ENABLED and RATELIMIT_DEFAULT environment variables.

Token expiration

Token lifetimes are set in the .env file:
JWT_ACCESS_TOKEN_EXPIRES=3600      # seconds — default 1 hour
JWT_REFRESH_TOKEN_EXPIRES=2592000  # seconds — default 30 days
The application reads these at startup and passes them to Flask-JWT-Extended. Changing these values requires a server restart.

Build docs developers (and LLMs) love