Authentication
The Blog API uses JSON Web Tokens (JWT) for authentication, implementing a dual-token strategy with access tokens and refresh tokens for enhanced security.
Overview
Access Token: Short-lived JWT used for API requests (sent in Authorization header)
Refresh Token: Long-lived JWT stored in HTTP-only cookie for token renewal
Security: Role-based access control with 'admin' and 'user' roles
Endpoints
Register User
Creates a new user account with optional role specification.
Endpoint: POST /auth/register
Authentication: None required
Request Body:
{
"email": "user@example.com",
"password": "securepassword123",
"role": "user" // Optional: "user" or "admin"
}
Success Response (201 Created):
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"_id": "60b7c8b4f9b3c12345678901",
"email": "user@example.com",
"username": "user123",
"role": "user",
"firstName": null,
"lastName": null,
"socialLinks": {},
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}
Error Responses:
400
- Validation errors (invalid email, password too short)403
- Admin registration denied for non-whitelisted email500
- Internal server error
Notes:
Password must be at least 8 characters
Admin registration requires email to be on server whitelist
Sets
refreshToken
HTTP-only cookie automatically
Login User
Authenticates existing user and returns access token.
Endpoint: POST /auth/login
Authentication: None required
Request Body:
{
"email": "user@example.com",
"password": "securepassword123"
}
Success Response (200 OK):
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"_id": "60b7c8b4f9b3c12345678901",
"email": "user@example.com",
"username": "user123",
"role": "user",
"firstName": "John",
"lastName": "Doe",
"socialLinks": {
"website": "https://johndoe.com",
"linkedin": "https://linkedin.com/in/johndoe"
},
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-20T15:45:00.000Z"
}
}
Error Responses:
400
- Invalid credentials or validation errors404
- User not found500
- Internal server error
Refresh Access Token
Generates a new access token using the refresh token cookie.
Endpoint: POST /auth/refresh-token
Authentication: Requires refreshToken
cookie (sent automatically by browser)
Request Body: None
Success Response (200 OK):
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Error Responses:
400
- Missing or invalid refresh token cookie401
- Expired or invalid refresh token500
- Internal server error
Usage Example:
// When your access token expires (401 error), call refresh endpoint
const response = await fetch("/api/v1/auth/refresh-token", {
method: "POST",
credentials: "include", // Important: includes cookies
});
if (response.ok) {
const { accessToken } = await response.json();
// Use new access token for subsequent requests
localStorage.setItem("accessToken", accessToken);
}
Logout User
Invalidates refresh token and clears cookie.
Endpoint: POST /auth/logout
Authentication: Requires both access token (header) and refresh token (cookie)
Headers:
Authorization: Bearer <access_token>
Request Body: None
Success Response (200 OK):
{
"message": "Logged out successfully"
}
Error Responses:
400
- Missing refresh token cookie401
- Invalid or expired tokens500
- Internal server error
Authentication Flow
1. Initial Authentication
sequenceDiagram
participant Client
participant API
participant Database
Client->>API: POST /auth/register or /auth/login
API->>Database: Validate credentials/create user
Database-->>API: User data
API-->>Client: Access token + Set refresh cookie
2. Making Authenticated Requests
// Include access token in all protected requests
const response = await fetch("/api/v1/users/current", {
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
3. Token Refresh Flow
sequenceDiagram
participant Client
participant API
Client->>API: Protected request with expired token
API-->>Client: 401 Unauthorized
Client->>API: POST /auth/refresh-token (with cookie)
API-->>Client: New access token
Client->>API: Retry original request with new token
API-->>Client: Success response
4. Logout
// Logout and clear tokens
await fetch("/api/v1/auth/logout", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
},
credentials: "include",
});
// Clear stored access token
localStorage.removeItem("accessToken");
Security Best Practices
Store Access Tokens Securely: Use secure storage (not localStorage in production)
Handle Token Expiration: Implement automatic refresh logic
Secure Cookies: Refresh tokens are stored in HTTP-only, secure cookies
Logout Properly: Always call logout endpoint to invalidate refresh tokens
HTTPS Only: Use HTTPS in production for token security
Role-Based Access
User Role (user
)
user
)Can manage own profile
Can create, view, and manage own comments
Can like/unlike blog posts
Can view published blog posts
Admin Role (admin
)
admin
)All user permissions
Can view all users and manage user accounts
Can create, edit, and delete blog posts
Can view draft blog posts
Can delete any comments
Common Authentication Errors
400
ValidationError
Invalid email/password format
Check request format
401
AuthenticationError
Missing/invalid/expired token
Refresh token or re-login
403
AuthorizationError
Insufficient permissions
Check user role
403
AuthorizationError
Admin registration denied
Contact admin for whitelist
Code Examples
Complete Authentication Implementation
class BlogAPIAuth {
constructor(baseURL) {
this.baseURL = baseURL;
this.accessToken = localStorage.getItem("accessToken");
}
async register(email, password, role = "user") {
const response = await fetch(`${this.baseURL}/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ email, password, role }),
});
if (response.ok) {
const data = await response.json();
this.accessToken = data.accessToken;
localStorage.setItem("accessToken", this.accessToken);
return data;
}
throw new Error(await response.text());
}
async login(email, password) {
const response = await fetch(`${this.baseURL}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ email, password }),
});
if (response.ok) {
const data = await response.json();
this.accessToken = data.accessToken;
localStorage.setItem("accessToken", this.accessToken);
return data;
}
throw new Error(await response.text());
}
async refreshToken() {
const response = await fetch(`${this.baseURL}/auth/refresh-token`, {
method: "POST",
credentials: "include",
});
if (response.ok) {
const data = await response.json();
this.accessToken = data.accessToken;
localStorage.setItem("accessToken", this.accessToken);
return data.accessToken;
}
return null;
}
async makeAuthenticatedRequest(url, options = {}) {
const headers = {
"Content-Type": "application/json",
...options.headers,
};
if (this.accessToken) {
headers.Authorization = `Bearer ${this.accessToken}`;
}
let response = await fetch(url, {
...options,
headers,
credentials: "include",
});
// Handle token expiration
if (response.status === 401) {
const newToken = await this.refreshToken();
if (newToken) {
headers.Authorization = `Bearer ${newToken}`;
response = await fetch(url, {
...options,
headers,
credentials: "include",
});
}
}
return response;
}
async logout() {
if (this.accessToken) {
await fetch(`${this.baseURL}/auth/logout`, {
method: "POST",
headers: { Authorization: `Bearer ${this.accessToken}` },
credentials: "include",
});
}
this.accessToken = null;
localStorage.removeItem("accessToken");
}
}
Last updated