Overview

I had the opportunity to test the IIIF Authentication API 2.0, so here are my notes.

https://iiif.io/api/auth/2.0/

I created the following demo site.

https://iiif-auth-nextjs.vercel.app/ja

The repository is available here.

https://github.com/nakamura196/iiif-auth-nextjs

The following explanation is AI-generated. Note that I was unable to get it working with Mirador, which remains a future task.

Overview

This article explains the authentication flow of IIIF Authentication API 2.0 in detail at the HTTP request/response level. We will trace what requests are sent and what responses are returned at each step.

Architecture Overview

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Client    │────▶│ IIIF Server │────▶│Auth Service │
│  (Browser)  │◀────│             │◀────│             │
└─────────────┘     └─────────────┘     └─────────────┘

Detailed Authentication Flow

Step 1: Initial Image Information Request (Unauthenticated)

Request:

GET /api/iiif/image/sample/info.json HTTP/1.1
Host: localhost:3001
Accept: application/json

Processing Flow (Server Side):

// app/api/iiif/image/[id]/info.json/route.ts
export async function GET(request: NextRequest) {
  // 1. Check Authorization header
  const authHeader = request.headers.get('authorization');
  let token = authHeader?.replace('Bearer ', '');

  // 2. Also check query parameters (fallback)
  if (!token) {
    token = request.nextUrl.searchParams.get('token') || '';
  }

  // 3. Token verification
  const isValid = token ? await verifyToken(token) : null;

  // 4. Return 401 if unauthenticated
  if (!isValid) {
    return NextResponse.json({
      error: 'Authentication required',
      service: [{
        "@context": "http://iiif.io/api/auth/2/context.json",
        "id": `${request.nextUrl.origin}/api/iiif/probe`,
        "type": "AuthProbeService2"
      }]
    }, { status: 401 });
  }
}

Response:

HTTP/1.1 401 Unauthorized
Content-Type: application/json
Access-Control-Allow-Origin: *

{
  "error": "Authentication required",
  "service": [{
    "@context": "http://iiif.io/api/auth/2/context.json",
    "id": "http://localhost:3001/api/iiif/probe",
    "type": "AuthProbeService2"
  }]
}

Step 2: Request to Probe Service

The client obtains the AuthProbeService2 URL from the 401 response and checks the authentication status.

Request:

GET /api/iiif/probe HTTP/1.1
Host: localhost:3001
Accept: application/json

Processing Flow (Server Side):

// app/api/iiif/probe/route.ts
export async function GET(request: NextRequest) {
  const authHeader = request.headers.get('authorization');
  const token = authHeader?.replace('Bearer ', '');

  if (token) {
    const payload = await verifyToken(token);
    if (payload) {
      // Authenticated case
      return NextResponse.json({
        "@context": "http://iiif.io/api/auth/2/context.json",
        "type": "AuthProbeResult2",
        "status": 200,
        "location": {
          "id": `${request.nextUrl.origin}/api/iiif/image/sample/info.json`,
          "type": "Image"
        }
      });
    }
  }

  // Unauthenticated case
  return NextResponse.json({
    "@context": "http://iiif.io/api/auth/2/context.json",
    "type": "AuthProbeResult2",
    "status": 401,
    "service": [{
      "@context": "http://iiif.io/api/auth/2/context.json",
      "id": `${request.nextUrl.origin}/api/iiif/access`,
      "type": "AuthAccessService2",
      "profile": "active",
      "label": "Login to IIIF Auth Demo",
      "service": [{
        "@context": "http://iiif.io/api/auth/2/context.json",
        "id": `${request.nextUrl.origin}/api/iiif/token`,
        "type": "AuthAccessTokenService2"
      }]
    }]
  }, { status: 401 });
}

Response (Unauthenticated):

{
  "@context": "http://iiif.io/api/auth/2/context.json",
  "type": "AuthProbeResult2",
  "status": 401,
  "service": [{
    "@context": "http://iiif.io/api/auth/2/context.json",
    "id": "http://localhost:3001/api/iiif/access",
    "type": "AuthAccessService2",
    "profile": "active",
    "label": "Login to IIIF Auth Demo",
    "service": [{
      "@context": "http://iiif.io/api/auth/2/context.json",
      "id": "http://localhost:3001/api/iiif/token",
      "type": "AuthAccessTokenService2"
    }]
  }]
}

Step 3: Initiating the Authentication Window

The client obtains the AuthAccessTokenService2 URL from the Probe Service response and opens a popup window.

Client-Side Processing:

// Obtaining auth service information
const authService = probeResult.service[0];
const tokenService = authService.service[0];

// Generate message ID (for identifying the response)
const messageId = crypto.randomUUID();

// Build token service URL
const tokenUrl = new URL(tokenService.id);
tokenUrl.searchParams.set('messageId', messageId);
tokenUrl.searchParams.set('origin', window.location.origin);

// Open popup window
const authWindow = window.open(
  tokenUrl.toString(),
  'iiif-auth',
  'width=600,height=600'
);

Generated URL:

http://localhost:3001/api/iiif/token?messageId=60f4420d-52c1-48ae-a24f-c3bb948fa0dc&origin=http://localhost:3001

Step 4: Token Service Redirect Processing

Request:

GET /api/iiif/token?messageId=60f4420d-52c1-48ae-a24f-c3bb948fa0dc&origin=http://localhost:3001 HTTP/1.1
Host: localhost:3001

The Token Service checks for existing authentication cookies. If not found, it redirects the user to the login page, preserving the messageId and origin as query parameters.

Step 5: Authentication on the Login Page

The user submits their credentials through the login form. On the server side, the credentials are verified and, if valid, a JWT token is generated and set as an HTTP-only cookie. The user is then redirected back to the Token Service endpoint.

Step 6: Token Delivery (postMessage)

After successful login, the client receives the token and sends it to the original window via the Token Service using postMessage.

Step 7: Token Reception in the Original Window

The main window receives the token via the postMessage event listener, validates the messageId, and stores the token for subsequent authenticated requests.

Step 8: Authenticated Image Request

With the token now available, the client includes it in the Authorization header for subsequent requests to the image API, which returns the full image information.

Token Persistence and Synchronization

Persistence via localStorage

The token is stored in localStorage to persist across page reloads and browser sessions (until the token expires).

Cross-Tab Synchronization

The storage event listener enables synchronization of the authentication state across multiple browser tabs.

CORS Configuration

Image API Endpoint

The Image API endpoint must include appropriate CORS headers to allow cross-origin requests, including Access-Control-Allow-Origin and Access-Control-Allow-Headers for the Authorization header.

Probe/Access Service

The Probe and Access services also require proper CORS configuration to support the cross-origin authentication flow.

Error Handling

Token Expiration

When a token expires, the server returns a 401 response. The client detects this, clears the stored token, and initiates a new authentication flow.

Authentication Error Handling

Various error conditions (invalid credentials, network errors, popup blocker) are handled with appropriate user feedback.

Security Considerations

1. Token Signature Verification

All tokens are verified using JWT signature validation to ensure they have not been tampered with.

2. HTTPS Usage (Production Environment)

In production, all communication should be over HTTPS to prevent token interception.

3. Origin Validation

The postMessage origin is validated to prevent cross-origin attacks.

Performance Optimization

1. Token Caching

Tokens are cached on the client side to avoid unnecessary re-authentication for each request.

2. Parallel Request Handling

Multiple concurrent requests share the same authentication flow to prevent duplicate authentication popups.

Troubleshooting

1. Popup Blocker

If the authentication popup is blocked, the application provides a fallback UI element for the user to manually trigger the authentication.

2. postMessage Reception Failure

If postMessage delivery fails (e.g., due to origin mismatch), detailed error logging helps diagnose the issue.

Summary

The IIIF Authentication API 2.0 implementation processes requests in the following flow:

  1. Initial access -> 401 with Probe Service
  2. Probe Service -> 401 with Access Service
  3. Token Service -> Login Page redirect
  4. Login -> JWT Token generation
  5. postMessage -> Token delivery to main window
  6. Authenticated Request -> Protected resource access

By implementing appropriate error handling and security measures at each step, a safe and user-friendly authentication system can be built.

References