Skip to main content

Login

Authenticate a user with email and password credentials and return JWT tokens.

πŸ“ Endpoint

POST /auth/login

πŸ“‹ Request Body

email
string
required
The user’s email address.
password
string
required
The user’s password.

πŸ“€ 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"
}

πŸ” Token Information

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';
}