Storage adapters
LTIkit needs two things persisted:
NonceStore— the OIDC handshake state. Must be single-use + TTL (replay defense). Short-lived.PlatformStore— the registry of trusted LMSs. Durable config.
Pick the backing store below. (See How it fits together for where these sit.)
Both stores, against any Prisma-supported database (SQLite, Postgres, MySQL, …) — SQLite means zero external service. Copy the two models into your schema; full setup on the Prisma adapter page.
import { PrismaClient } from '@prisma/client'import { prismaPlatformStore, prismaNonceStore } from '@ltikit/adapter-prisma'
const prisma = new PrismaClient()
// in createLti({ ... }):platforms: prismaPlatformStore(prisma),nonces: prismaNonceStore(prisma),No hard dependency on @prisma/client — the client is accepted structurally, so any generated
PrismaClient matching the two models works.
Redis / Upstash — great for serverless nonces (single-use via atomic GETDEL). Redis is the nonce
store; platforms are durable config, so keep them in a durable store (Prisma, Supabase, or your own).
Full setup on the Redis adapter page.
import { redisNonceStore, fromUpstash } from '@ltikit/adapter-redis'import { Redis } from '@upstash/redis'
const nonces = redisNonceStore(fromUpstash(Redis.fromEnv()))// platforms: prismaPlatformStore(...) / supabasePlatformStore(...) or your ownAlso ships fromIoRedis and fromNodeRedis.
Postgres via Supabase. Full setup (schema + CLI + RLS) is on the Supabase adapter page.
import { createClient } from '@supabase/supabase-js'import { supabasePlatformStore, supabaseNonceStore, type SupabaseLike } from '@ltikit/adapter-supabase'
const admin = createClient(url, serviceRoleKey, { auth: { persistSession: false } })const client = admin as unknown as SupabaseLike
// in createLti({ ... }):platforms: supabasePlatformStore(client),nonces: supabaseNonceStore(client),In-memory — dev and tests only (state is per-process; can’t enforce single-use across serverless invocations).
import { MemoryNonceStore, MemoryPlatformStore } from '@ltikit/adapter-memory'
const nonces = new MemoryNonceStore()const platforms = new MemoryPlatformStore([/* seed Platform[] */])Implement the interfaces against any store. consume MUST be atomic fetch-and-delete (single-use).
import type { NonceStore, PlatformStore } from '@ltikit/core'
const nonces: NonceStore = { async create(rec) { /* persist rec with rec.ttlSec expiry */ }, async consume(state) { /* atomic get+delete; null if missing/expired */ return null },}const platforms: PlatformStore = { async find(iss, clientId) { /* look up by issuer (+clientId); map to Platform */ return null },}Verify with the shared conformance kit: import { nonceStoreConformance } from '@ltikit/core/testing'.