ghost-mcp/contracts/admin-api-auth.md

4.9 KiB

Ghost Admin API JWT Authentication Documentation

Overview

Ghost Admin API uses JWT (JSON Web Token) authentication for secure, server-side access to read/write operations.

Authentication Flow

1. API Key Structure

Format: {id}:{secret}

  • ID: Used as the kid (key identifier) in JWT header
  • Secret: Used to sign the JWT token
  • Source: Generated from Ghost Admin → Settings → Integrations → Custom Integration

Example: 507f1f77bcf86cd799439011:1234567890abcdef1234567890abcdef12345678

2. JWT Token Generation

Required Headers

{
  "alg": "HS256",
  "kid": "{api_key_id}",
  "typ": "JWT"
}

Required Payload

{
  "exp": {timestamp_plus_5_minutes},
  "iat": {current_timestamp},
  "aud": "/admin/"
}

Constraints

  • Token Expiration: Maximum 5 minutes from generation
  • Algorithm: HS256 (HMAC SHA-256)
  • Audience: Must be /admin/
  • Key ID: Must match the API key ID

3. HTTP Request Format

Required Headers

Authorization: Ghost {jwt_token}
Accept-Version: v5.0
Content-Type: application/json

Example Request

GET /ghost/api/admin/posts/ HTTP/1.1
Host: localhost:2368
Authorization: Ghost eyJhbGciOiJIUzI1NiIsImtpZCI6IjUwN2YxZjc3YmNmODZjZDc5OTQzOTAxMSIsInR5cCI6IkpXVCJ9...
Accept-Version: v5.0

Implementation Requirements

Node.js Implementation (for MCP Server)

Dependencies

{
  "jsonwebtoken": "^9.0.0"
}

Token Generation Function

const jwt = require('jsonwebtoken');

function generateAdminApiToken(apiKey) {
  // Split the key into ID and secret
  const [id, secret] = apiKey.split(':');

  // Prepare header
  const header = {
    alg: 'HS256',
    kid: id,
    typ: 'JWT'
  };

  // Prepare payload
  const now = Math.floor(Date.now() / 1000);
  const payload = {
    exp: now + (5 * 60), // 5 minutes from now
    iat: now,
    aud: '/admin/'
  };

  // Generate token
  const token = jwt.sign(payload, Buffer.from(secret, 'hex'), { header });
  return token;
}

Usage Example

const adminApiKey = 'your_admin_api_key_here';
const token = generateAdminApiToken(adminApiKey);

const response = await fetch('http://localhost:2368/ghost/api/admin/posts/', {
  headers: {
    'Authorization': `Ghost ${token}`,
    'Accept-Version': 'v5.0',
    'Content-Type': 'application/json'
  }
});

Token Refresh Strategy

Since tokens expire after 5 minutes:

  1. Generate new token for each request (simplest)
  2. Cache token with expiration tracking (more efficient)
  3. Regenerate on 401 response (error-driven)
class GhostAdminAuth {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.token = null;
    this.tokenExpiry = null;
  }

  generateToken() {
    const [id, secret] = this.apiKey.split(':');
    const now = Math.floor(Date.now() / 1000);

    const payload = {
      exp: now + (4 * 60), // 4 minutes (buffer)
      iat: now,
      aud: '/admin/'
    };

    this.token = jwt.sign(payload, Buffer.from(secret, 'hex'), {
      header: { alg: 'HS256', kid: id, typ: 'JWT' }
    });
    this.tokenExpiry = now + (4 * 60);

    return this.token;
  }

  getValidToken() {
    const now = Math.floor(Date.now() / 1000);
    if (!this.token || now >= this.tokenExpiry) {
      return this.generateToken();
    }
    return this.token;
  }

  getAuthHeaders() {
    return {
      'Authorization': `Ghost ${this.getValidToken()}`,
      'Accept-Version': 'v5.0',
      'Content-Type': 'application/json'
    };
  }
}

Error Handling

Invalid Token

Response: 401 Unauthorized

{
  "errors": [
    {
      "message": "Authorization failed",
      "context": "Unable to determine the authenticated user or integration...",
      "type": "UnauthorizedError"
    }
  ]
}

Expired Token

Response: 401 Unauthorized Action: Generate new token and retry

Invalid API Key

Response: 401 Unauthorized Action: Verify API key format and permissions

Security Considerations

  1. Keep API Key Secret: Never expose in client-side code
  2. Server-Side Only: JWT generation must happen server-side
  3. Short Token Lifespan: Maximum 5 minutes reduces exposure window
  4. HTTPS in Production: Always use HTTPS for API requests
  5. Key Rotation: Regularly rotate API keys in production

Testing Strategy

Manual Testing

  1. Generate API key from Ghost Admin
  2. Create JWT token using the implementation
  3. Test various Admin API endpoints
  4. Verify token expiration handling

Automated Testing

  1. Mock JWT generation for unit tests
  2. Use test API keys for integration tests
  3. Test token refresh logic
  4. Test error handling scenarios

Implementation Status

  • Authentication flow documented
  • JWT generation requirements identified
  • Node.js implementation planned
  • Implementation pending API key generation
  • Testing pending API key availability