Skip to main content
Documentation

API V2 Reference β€” JWT Authentication

V2 has the same task endpoints as V1 but requires a valid JWT Bearer token on every request. That includes labels, comment sub-resources, and updatedAt behaviour. Use this API to practice testing authenticated REST services.

Base URL

bash
https://api.testauto.app/api/v2

Authentication

Test Users

  • admin / admin123 β€” role: ADMIN
  • user / user123 β€” role: USER
  • testuser / test123 β€” role: USER

POST /auth/login

Exchange credentials for a JWT token.

Request

bash
curl -X POST "https://api.testauto.app/api/v2/auth/login" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "user",
    "password": "user123"
  }'

Response 200 OK

json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "username": "user",
  "expiresIn": 3600
}

Response 401 Unauthorized

json
{
  "timestamp": "2026-02-02T16:00:00Z",
  "status": 401,
  "error": "Unauthorized",
  "message": "Invalid username or password"
}

POST /auth/refresh

Get a new token using the current (non-expired) token.

Request

bash
curl -X POST "https://api.testauto.app/api/v2/auth/refresh" \
  -H "Authorization: Bearer YOUR_CURRENT_TOKEN"

Response 200 OK

json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 3600
}

Using the Token

Include the token in the Authorization header on all task endpoints:

bash
curl "https://api.testauto.app/api/v2/tasks" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"

A missing or invalid token returns 401 Unauthorized.

Task Endpoints

V2 task endpoints are identical to V1 in request/response shape, but every call must include the Authorization header.

  • GET /tasks β€” list tasks (pagination, filtering, sorting)
  • GET /tasks/{id} β€” get single task
  • POST /tasks β€” create task
  • PUT /tasks/{id} β€” replace task
  • DELETE /tasks/{id} β€” delete task
  • POST /tasks/{id}/comments β€” create comment
  • PUT /tasks/{id}/comments/{commentId} β€” update comment
  • DELETE /tasks/{id}/comments/{commentId} β€” delete comment
  • GET /tasks/summary β€” statistics

The request and response shapes follow V1: priorities include URGENT, search covers labels, and comments use the text field.

Authenticated list example

bash
# Step 1: login
TOKEN=$(curl -s -X POST "https://api.testauto.app/api/v2/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"user","password":"user123"}' | jq -r .token)

# Step 2: use token
curl "https://api.testauto.app/api/v2/tasks?status=TODO" \
  -H "Authorization: Bearer $TOKEN"

Testing Authentication in Code

Successful Login

javascript
test('login with valid credentials returns token', async () => {
  const response = await fetch('https://api.testauto.app/api/v2/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: 'user', password: 'user123' }),
  });

  expect(response.status).toBe(200);
  const data = await response.json();
  expect(data.token).toBeDefined();
  expect(data.username).toBe('user');
  expect(data.expiresIn).toBe(3600);
});

Invalid Credentials

javascript
test('login fails with wrong password', async () => {
  const response = await fetch('https://api.testauto.app/api/v2/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: 'user', password: 'wrong' }),
  });

  expect(response.status).toBe(401);
});

Accessing Protected Endpoint

javascript
test('authenticated request returns tasks', async () => {
  const loginResp = await fetch('https://api.testauto.app/api/v2/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: 'user', password: 'user123' }),
  });
  const { token } = await loginResp.json();

  const tasksResp = await fetch('https://api.testauto.app/api/v2/tasks', {
    headers: { Authorization: `Bearer ${token}` },
  });

  expect(tasksResp.status).toBe(200);
  const data = await tasksResp.json();
  expect(Array.isArray(data.content)).toBe(true);
});

Missing or Expired Token

javascript
test('request without token returns 401', async () => {
  const response = await fetch('https://api.testauto.app/api/v2/tasks');
  expect(response.status).toBe(403);
});

test('tampered token is rejected', async () => {
  const loginResp = await fetch('https://api.testauto.app/api/v2/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: 'user', password: 'user123' }),
  });
  const { token } = await loginResp.json();

  // Modify last character to tamper with the signature
  const tampered = token.slice(0, -1) + 'X';
  const response = await fetch('https://api.testauto.app/api/v2/tasks', {
    headers: { Authorization: `Bearer ${tampered}` },
  });

  expect(response.status).toBe(403);
});

Authenticated Comment Lifecycle

javascript
test('create and update a comment with JWT authentication', async () => {
  const loginResp = await fetch('https://api.testauto.app/api/v2/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: 'user', password: 'user123' }),
  });
  const { token } = await loginResp.json();

  const createComment = await fetch('https://api.testauto.app/api/v2/tasks/1/comments', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ text: 'JWT-protected comment' }),
  });

  expect(createComment.status).toBe(201);
});

Token Structure

json
// JWT payload (base64-decoded)
{
  "sub": "user",
  "iat": 1706877600,   // issued at (Unix timestamp)
  "exp": 1706881200,   // expires at (iat + 3600s)
  "role": "USER"
}

Tokens expire after 3600 seconds (1 hour). Use POST /auth/refresh to extend.

HTTP Status Codes

  • 200 OK β€” Request succeeded
  • 201 Created β€” Resource created
  • 204 No Content β€” Succeeded, no body
  • 400 Bad Request β€” Validation error
  • 401 Unauthorized β€” Invalid login credentials
  • 403 Forbidden β€” Missing, invalid, or rejected token for protected endpoints
  • 404 Not Found β€” Resource does not exist