Overview
I had the opportunity to test the IIIF Authentication API 2.0, so here are my notes.
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:
- Initial access -> 401 with Probe Service
- Probe Service -> 401 with Access Service
- Token Service -> Login Page redirect
- Login -> JWT Token generation
- postMessage -> Token delivery to main window
- 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.