Auth Token
Overview
The Auth Token integration enables single sign-on (SSO) between your platform and Publica.la stores. Users authenticated in your system (LMS, e-commerce, CMS, etc.) can access Publica.la content without logging in again.
How it works: Your platform generates a signed JWT containing user information, redirects the user to Publica.la with that token, and we validate it to grant access automatically. New users are created on first authentication; existing users are recognized by their uuid.
Not the right fit? Consider IP Authentication for IP-based access, LTI for LMS integrations, or URL Referrer for domain or URL-based access.
Setup
-
Generate a secret key — Create a 32-character secret that you'll use to sign JWTs.
-
Contact support with the following configuration:
Parameter Description keyThe 32-character secret you generated issuerYour platform identifier (e.g., platform-name). Must matchissclaim in tokens.redirect_urlWhere to send users on auth errors logout_urlWhere to send users after logging out -
Wait for confirmation before testing your integration.
Token format
Tokens must be signed with HS256 (HMAC-SHA256). Other algorithms are rejected.
Example payload
{
"iss": "platform-name",
"aud": "farfalla",
"sub": "user",
"jti": "550e8400-e29b-41d4-a716-446655440000",
"exp": 1699999960,
"user": {
"uuid": "user-123",
"email": "user@example.com",
"picture_url": "https://example.com/avatar.jpg"
},
"intended_url": "https://store.publica.la/reader/my-ebook"
}
Claims reference
Required JWT claims
| Claim | Type | Description |
|---|---|---|
iss | string | Your platform identifier. Must match configured issuer. |
aud | string | Must be "farfalla". |
sub | string | Must be "user". |
jti | string | Unique token ID. Use UUID v4 to prevent replay. |
exp | number | Expiration timestamp (Unix time). We recommend 60 seconds for real-time redirects. |
User object (user claim)
| Field | Required | Type | Description |
|---|---|---|---|
uuid | Yes | string | Unique identifier for the user in your system. 1-200 characters. Stored as external_id in Publica.la. |
email | No | string | User's email address. Must be valid format if provided. Updated on each login if changed. |
picture_url | No | string | Profile picture URL. Max 200 characters. Only set on first login, not updated afterwards. |
accept_terms_and_policies | No | boolean | If true, automatically accepts terms on first login. |
Optional claims
| Claim | Type | Description |
|---|---|---|
intended_url | string | Where to redirect after successful login. Must be a valid URL. Defaults to /library. |
reader_exit_url | string | Custom URL for the reader's exit button. Stored in session for the reader to use. |
Endpoint
URL
https://{store-domain}/auth/token
Methods
Both GET and POST are supported.
Passing the token
The token can be provided in two ways:
- Query Parameter
- HTTP Header
https://{store-domain}/auth/token?external-auth-token={JWT}
Recommended for browser redirects. JWT tokens are URL-safe, no encoding needed.
Tokens in URLs appear in browser history, server logs, and may leak via Referrer headers. Use short expiration times (60 seconds) to mitigate risk.
GET /auth/token HTTP/1.1
Host: {store-domain}
external-auth-token: {JWT}
More secure as tokens don't appear in logs or browser history. Requires JavaScript or server-side implementation.
Response behavior
| Scenario | HTTP Response | Destination |
|---|---|---|
| Valid token | 302 Redirect | intended_url from token, or /library if not specified |
| Invalid token | 302 Redirect | redirect_url with error query params |
| Invalid user data | 302 Redirect | redirect_url with error query params |
| Missing store configuration | 422 JSON | Error message returned |
User management
View detailed authentication flow
New users
When a user authenticates for the first time (no existing user with that uuid):
- A new user account is created with the provided
uuidasexternal_id emailandpicture_urlare set if provided in the token- If
accept_terms_and_policies: true, terms are automatically accepted - User is logged in and redirected to their destination
Returning users
When a user with an existing uuid authenticates:
- User is found by their
external_id(youruuid) emailis updated if the token contains a different valuepicture_urlis not updated (only set on creation)- User is logged in and redirected
Email uniqueness
Each email address can only be associated with one uuid. If you send an email that already belongs to a different user, authentication will fail with an invalid-user error.
Code examples
- PHP (lcobucci/jwt v5)
- Node.js
- Python
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use DateTimeImmutable;
$config = Configuration::forSymmetricSigner(
new Sha256(),
InMemory::plainText($_ENV['JWT_SECRET'])
);
$now = new DateTimeImmutable();
$token = $config->builder()
->issuedBy('platform-name') // iss - must match configured issuer
->permittedFor('farfalla') // aud - always "farfalla"
->relatedTo('user') // sub - always "user"
->identifiedBy(bin2hex(random_bytes(16))) // jti - unique token ID
->expiresAt($now->modify('+60 seconds')) // exp - short expiration for security
->withClaim('user', [
'uuid' => $userId,
'email' => $userEmail,
'picture_url' => $userAvatar,
])
->withClaim('intended_url', 'https://store.publica.la/reader/my-ebook')
->getToken($config->signer(), $config->signingKey());
$jwt = $token->toString();
// Redirect user to Publica.la
header('Location: https://store.publica.la/auth/token?external-auth-token=' . $jwt);
exit;
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const token = jwt.sign(
{
iss: 'platform-name', // must match configured issuer
aud: 'farfalla', // always "farfalla"
sub: 'user', // always "user"
jti: crypto.randomUUID(), // unique token ID
user: {
uuid: userId,
email: userEmail,
picture_url: userAvatar,
},
intended_url: 'https://store.publica.la/reader/my-ebook',
},
process.env.JWT_SECRET,
{
algorithm: 'HS256',
expiresIn: 60, // 60 seconds
}
);
// Redirect user to Publica.la
res.redirect(`https://store.publica.la/auth/token?external-auth-token=${token}`);
import jwt
import os
from uuid import uuid4
from time import time
token = jwt.encode(
{
'iss': 'platform-name', # must match configured issuer
'aud': 'farfalla', # always "farfalla"
'sub': 'user', # always "user"
'jti': str(uuid4()), # unique token ID
'exp': int(time()) + 60, # 60 seconds from now
'user': {
'uuid': user_id,
'email': user_email,
'picture_url': user_avatar,
},
'intended_url': 'https://store.publica.la/reader/my-ebook',
},
os.environ['JWT_SECRET'],
algorithm='HS256'
)
# Redirect user to Publica.la
return redirect(f'https://store.publica.la/auth/token?external-auth-token={token}')
Error handling
When authentication fails, users are redirected to your configured redirect_url with error information in query parameters:
https://your-redirect-url.com?external-auth-token-error={code}&external-auth-token-error-details={base64-json}
Error codes
| Code | Description |
|---|---|
invalid-token | Token validation failed (expired, bad signature, wrong issuer/audience/subject) |
invalid-user | User data validation failed (invalid email format, missing uuid, uuid too long) |
Decoding error details
The external-auth-token-error-details parameter contains Base64-encoded JSON with specific error messages:
- JavaScript
- PHP
- Python
const details = JSON.parse(atob(errorDetails));
console.log(details);
$details = json_decode(base64_decode($errorDetails), true);
print_r($details);
import base64
import json
details = json.loads(base64.b64decode(error_details))
print(details)
Example error responses
Token validation error:
{
"token": {
"exp": "Token is expired or `exp` attribute not present."
}
}
User validation error:
{
"email": ["The email must be a valid email address."]
}
Email conflict error:
{
"uuid": ["This email is already attached to UUID abc-123."]
}
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| "Token is expired" | exp timestamp is in the past | Ensure server clock is synchronized (NTP). Use 60-second expiration. |
| "Invalid signature" | Signing key doesn't match | Verify key matches exactly what was configured in setup. |
| "Invalid issuer" | iss claim mismatch | Ensure iss matches the configured issuer exactly (case-sensitive). |
| "Invalid audience" | aud claim wrong | Must be exactly "farfalla". |
| "Email already attached to UUID" | Email registered to different user | Use the correct uuid for that email, or use a different email. |
| 422 error | Store not configured | Contact support to complete Auth Token setup. |
Debugging tips
- Inspect your token — Paste it at jwt.io to see decoded header and payload
- Check expiration — Ensure
expis in the future when the request reaches Publica.la - Verify fixed values —
audmust be"farfalla",submust be"user" - Decode errors — Use base64decode.org to read error details
JWT Generator
Generate test tokens to verify your integration:
Security best practices
- Use short expiration times — 60 seconds is recommended for tokens passed in URLs
- Store secrets securely — Use environment variables, never commit keys to version control
- Use HTTPS only — Never transmit tokens over unencrypted connections
- Prefer headers when possible — HTTP headers don't appear in browser history or server logs
- Generate unique
jtivalues — Use UUID v4 for each token to enable replay detection - Rotate keys periodically — Contact support to update your signing key
Tips for integrators
- Anonymous users — Omit
emailto create anonymous user accounts - Direct content access — Use
intended_urlto send users directly to a specific book or page - Custom reader exit — Use
reader_exit_urlto control where users go when closing the reader - No URL encoding needed — JWT tokens are URL-safe by design
- Email updates — Users are matched by
uuid, so if an email changes in your system, just send the new email and it will be updated automatically