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 secret of at least 32 bytes (256 bits) that you'll use to sign JWTs. Longer secrets are accepted.
-
Contact support with the following configuration:
Parameter Description keyThe secret you generated (at least 32 bytes / 256 bits) 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",
},
"intended_url": "https://{store-domain}/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. |
exp is validated with no tolerance: a token is rejected as soon as exp is in the past relative to Publica.la's server clock, with zero leeway. A token sent without exp is treated as expired. Keep your server clock synchronized (NTP), and account for network latency when choosing the expiration window.
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. |
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 three ways. When more than one is present, Publica.la reads them in this order: header, then cookie, then query parameter.
- Query Parameter
- HTTP Header
- Cookie
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.
GET /auth/token HTTP/1.1
Host: {store-domain}
Cookie: external-auth-token={JWT}
Useful when an upstream proxy or your platform sets the cookie on the user's browser before the redirect.
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 emailis 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 value- 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,
])
->withClaim('intended_url', 'https://{store-domain}/reader/my-ebook')
->getToken($config->signer(), $config->signingKey());
$jwt = $token->toString();
// Redirect user to Publica.la
header('Location: https://{store-domain}/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,
},
intended_url: 'https://{store-domain}/reader/my-ebook',
},
process.env.JWT_SECRET,
{
algorithm: 'HS256',
expiresIn: 60, // 60 seconds
}
);
// Redirect user to Publica.la
res.redirect(`https://{store-domain}/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,
},
'intended_url': 'https://{store-domain}/reader/my-ebook',
},
os.environ['JWT_SECRET'],
algorithm='HS256'
)
# Redirect user to Publica.la
return redirect(f'https://{store-domain}/auth/token?external-auth-token={token}')