Authenticate a user with email and password credentials and return JWT tokens.
π Endpoint
π Request Body
The userβs email address.
π€ Request Example
curl -X POST https://api.posthoot.com/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "john.doe@example.com",
"password": "secure_password123"
}'
const response = await fetch('https://api.posthoot.com/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'john.doe@example.com',
password: 'secure_password123'
})
});
const data = await response.json();
import requests
response = requests.post(
'https://api.posthoot.com/auth/login',
json={
'email': 'john.doe@example.com',
'password': 'secure_password123'
}
)
data = response.json()
π₯ Response
Success (200 OK)
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user_123",
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"role": "ADMIN",
"team_id": "team_456",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
},
"team": {
"id": "team_456",
"name": "John's Team",
"created_at": "2024-01-01T00:00:00Z"
},
"expires_in": 86400
}
Error Responses
Invalid Credentials (401 Unauthorized)
{
"error": "unauthorized",
"message": "Invalid email or password",
"code": "INVALID_CREDENTIALS"
}
Account Locked (401 Unauthorized)
{
"error": "unauthorized",
"message": "Account is locked due to too many failed attempts",
"code": "ACCOUNT_LOCKED"
}
Missing Fields (400 Bad Request)
{
"error": "validation_error",
"message": "Email and password are required",
"code": "VALIDATION_ERROR"
}
Access Token
- Lifetime: 24 hours
- Usage: Include in
Authorization: Bearer <token> header
- Purpose: Authenticate API requests
Refresh Token
- Lifetime: 7 days
- Usage: Obtain new access tokens
- Purpose: Maintain session without re-authentication
π‘οΈ Security Features
Rate Limiting
- Login attempts: 5 per minute per IP
- Account lockout: Temporary lock after 10 failed attempts
- Lockout duration: 15 minutes
Password Security
- Bcrypt hashing: Passwords are securely hashed
- Salt: Each password has a unique salt
- Timing attacks: Constant-time comparison
Session Management
- Token rotation: Refresh tokens are rotated on use
- Revocation: Tokens can be revoked if compromised
- Audit trail: Login attempts are logged
π Using the Tokens
Make API Requests
const response = await fetch('https://api.posthoot.com/api/v1/campaigns', {
headers: {
'Authorization': `Bearer ${data.access_token}`
}
});
Refresh Token
const refreshResponse = await fetch('https://api.posthoot.com/auth/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
refresh_token: data.refresh_token
})
});
π¨ Error Handling
Handle Authentication Errors
async function login(email, password) {
try {
const response = await fetch('/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
if (!response.ok) {
const error = await response.json();
switch (error.code) {
case 'INVALID_CREDENTIALS':
throw new Error('Invalid email or password');
case 'ACCOUNT_LOCKED':
throw new Error('Account is temporarily locked');
default:
throw new Error(error.message);
}
}
return await response.json();
} catch (error) {
console.error('Login error:', error);
throw error;
}
}
π Best Practices
1. Secure Token Storage
// Store tokens securely (not in localStorage)
const secureStorage = {
setTokens: (tokens) => {
// Use secure HTTP-only cookies or encrypted storage
document.cookie = `access_token=${tokens.access_token}; Secure; HttpOnly; SameSite=Strict`;
document.cookie = `refresh_token=${tokens.refresh_token}; Secure; HttpOnly; SameSite=Strict`;
},
getTokens: () => {
// Retrieve tokens from secure storage
return {
access_token: getCookie('access_token'),
refresh_token: getCookie('refresh_token')
};
}
};
2. Automatic Token Refresh
class TokenManager {
constructor() {
this.accessToken = null;
this.refreshToken = null;
}
async refreshAccessToken() {
const response = await fetch('/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: this.refreshToken })
});
if (response.ok) {
const data = await response.json();
this.accessToken = data.access_token;
return this.accessToken;
} else {
// Redirect to login
window.location.href = '/login';
}
}
async makeAuthenticatedRequest(url, options = {}) {
if (!this.accessToken) {
throw new Error('No access token available');
}
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.accessToken}`
}
});
if (response.status === 401) {
// Token expired, try to refresh
await this.refreshAccessToken();
return this.makeAuthenticatedRequest(url, options);
}
return response;
}
}
3. Handle Logout
async function logout() {
// Clear tokens from storage
secureStorage.clearTokens();
// Optionally call logout endpoint to invalidate tokens
try {
await fetch('/auth/logout', {
method: 'POST',
headers: {
'Authorization': `Bearer ${currentToken}`
}
});
} catch (error) {
console.error('Logout error:', error);
}
// Redirect to login page
window.location.href = '/login';
}