Auth API
Login via Google OAuth 2.0 plus JWT issuance and refresh. See API Conventions for shared rules.
Base path: /api/v1/auth
| Method | Path | Summary | Auth |
|---|---|---|---|
| GET | /google |
Start Google OAuth | Not required |
| GET | /google/callback |
Google OAuth callback | Not required |
| POST | /refresh |
Refresh the access token | Not required (authenticated by the refresh token) |
| POST | /logout |
Log out | Required |
| GET | /me |
Get the current user | Required |
Start Google OAuth
URI
Request
No parameters.
Response (302 Found)
Redirects to Google's consent screen. No body.
Note
Swagger UI cannot follow the redirect โ open this URL directly in a new tab instead.
Google OAuth Callback
Receives Google's redirect, exchanges the authorization code for tokens, creates or updates the user, and redirects back to the frontend.
URI
Query Parameters
| Field | Required | Rules |
|---|---|---|
code |
โ | OAuth authorization code, at least 1 character |
state |
- | OAuth state parameter |
Response (302 Found)
| Case | Redirect target |
|---|---|
| Success | {FRONTEND_URL}/login?token={accessToken} |
| Failure | {FRONTEND_URL}/login?error=auth_failed |
Failures also return 302, and internal error details never appear in the URL (they are logged only).
Errors
| Description | Status code | Status name |
|---|---|---|
code missing or malformed |
400 | Bad Request |
Flow
sequenceDiagram
participant G as Google
participant API as Backend API
participant DB as PostgreSQL
participant FE as Frontend
G->>API: GET /google/callback?code=...
API->>G: Exchange code for an access token
G-->>API: Profile (google_id / email / name / picture)
API->>DB: Look up the user by google_id
alt Not registered
API->>DB: Create users row (role = member)
else Registered
API->>DB: Update the profile
end
API->>API: Sign a JWT (HS256)
API-->>FE: 302 {FRONTEND_URL}/login?token=...
Refresh Access Token
URI
Request Body
The request body is JSON.
Validation Rules
| Field | Rules |
|---|---|
refreshToken |
Required, at least 1 character |
Response (200 OK)
{
"success": true,
"status": "success",
"statusCode": 200,
"timestamp": "2026-01-15T10:00:00.000Z",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "refresh_token_here",
"expiresIn": 3600
},
"path": "/api/v1/auth/refresh",
"method": "POST"
}
Errors
| Description | Status code | Status name |
|---|---|---|
| Malformed body | 400 | Bad Request |
| Refresh token invalid or expired | 401 | Unauthorized |
Log Out
URI
Authentication
Requires Authorization: Bearer <access_token>.
Response (200 OK)
{
"success": true,
"status": "success",
"statusCode": 200,
"data": { "message": "Logged out successfully" },
"path": "/api/v1/auth/logout",
"method": "POST"
}
Errors
| Description | Status code | Status name |
|---|---|---|
| Token missing or invalid | 401 | Unauthorized |
Tokens are not invalidated server-side
With stateless JWTs, logout means discarding the token on the client. This endpoint does not blacklist anything, so already-issued tokens remain valid until they expire.
Get Current User
URI
Authentication
Requires Authorization: Bearer <access_token>.
Response (200 OK)
{
"success": true,
"status": "success",
"statusCode": 200,
"data": {
"id": 1,
"email": "user@example.com",
"name": "John Doe",
"picture": "https://example.com/avatar.jpg",
"role": "member",
"createdAt": "2026-01-15T09:00:00Z"
},
"path": "/api/v1/auth/me",
"method": "GET"
}
| Field | Type | Description |
|---|---|---|
id |
integer | User ID |
email |
string | Email address |
name |
string | Display name |
picture |
string | null | Profile image URL (avatar_url in the DB) |
role |
enum | admin / member / viewer |
createdAt |
string(date-time) | Registration timestamp |
Errors
| Description | Status code | Status name |
|---|---|---|
| Token missing or invalid | 401 | Unauthorized |