Skip to main content

Building a Multi-Tenant LTI® Tool

The short answer

The moment your tool serves a second institution, several things that worked stop being correct. The core rule: a tenant is identified by issuer and client ID together, never by issuer alone, and no identifier from a launch is unique across platforms.

Most multi-tenancy bugs in LTI® tools are one of those two assumptions surviving from the single-customer version.

Keying a tenant correctly

A launch identifies its origin with an issuer naming the platform, a client ID naming your registration, and a deployment ID naming the installation.

Look up registrations on issuer + client ID. Issuer alone is not enough, because several institutions can present the same one — Canvas commonly sends https://canvas.instructure.com regardless of the institution's own domain, so a tool serving twenty Canvas customers sees one issuer twenty times.

And on Blackboard, the deployment ID becomes load-bearing: its multi-tenant model lets several institutions share a registration entirely, arriving with the same issuer and client ID. There, deployment ID is the only thing distinguishing them. Store it from the start — the Blackboard article has the detail.

User identity is scoped, not global

The sub claim identifies a user within its issuer. It is not globally unique, and two platforms can send the same value for different people.

So the stored identity is the pair — issuer plus sub — and a unique constraint on sub alone is a bug that surfaces as one institution's user being handed another's account.

The same applies to course and resource link identifiers. All of them are scoped to their platform — 1EdTech's guidance on selecting a user ID is worth reading before you design the schema.

And do not fall back to email. Addresses are reused and changed, many platforms send none, and the same address at two institutions is not necessarily one person. The SSO article covers account linking properly.

What breaks at customer two

The list, in the order people hit it:

Configuration that was global. Anything stored once — a platform key, an endpoint, a setting — needs a tenant on it.

Cached platform keys. Key sets cached without a tenant will serve one institution's key for another's launch. Verification fails in a way that looks like a signature problem.

Nonce storage. Nonces should be scoped per platform, or one platform can consume another's — a correctness problem and a subtle denial of service.

Access tokens. Service tokens are issued per platform and per scope. A shared cache hands the wrong token to the wrong LMS, which the LMS rejects.

Onboarding assumptions. The first customer was set up by hand. The twentieth needs it to be a process, which is the argument for dynamic registration.

Isolate the keys

Not required by the specification, worth doing anyway: a key pair per registration rather than one for the whole tool.

The reason is blast radius. With a single key, a compromise affects every institution you serve at once. With a key per registration, it affects one.

The cost is a key set with several entries rather than one, which is what the kid in a token header is for.

Where LTIAAS fits

Multi-tenancy is the architecture rather than a feature. Registrations are stored per account, platform lookups are keyed on issuer and client ID together, nonces are scoped per platform, and access tokens are cached per platform and scope.

Each registered platform gets its own key pair, published in your key set under a kid identifying that registration — so a key is scoped to one institution rather than to your whole tool.

Launch data arrives with the platform identified, so your application stores the pair without deriving it:

const { platform, user } = await axios.get('https://your.ltiaas.com/api/idtoken', {
headers: { Authorization: `LTIK-AUTH-V2 ${API_KEY}:${ltik}` }
}).then(r => r.data)
// key your records on platform + user.id, never user.id alone

What remains yours is your own data model: making sure a tenant identifier reaches every table, every cache and every query, which no service can do on your behalf.

Common questions

How do I identify which institution a launch came from?

By the issuer and client ID together, not the issuer alone. Several institutions can present the same issuer, so a lookup keyed only on it can hand one tenant another's configuration.

Do I need separate keys per platform?

Not required, but it limits blast radius. A key pair per registration means a compromise affects one institution rather than every one you serve.

What breaks when the second institution arrives?

Anything that assumed one registration. Global configuration, user identifiers treated as unique, cached keys stored without a tenant, and any lookup keyed on issuer alone.

Are user identifiers unique across platforms?

No. The sub claim is unique within an issuer only. Two institutions can legitimately send the same value for different people, so identity must be stored as the pair.

Next

All trademarks, logos, and service marks displayed on this website are the property of their respective owners. LTIAAS is a trademark of GatherAct, LLC, doing business as LTIAAS. Learning Tools Interoperability (LTI)® and LTI® are trademarks of 1EdTech Consortium, Inc. LTIAAS is not affiliated with, endorsed or sponsored by 1EdTech Consortium, Inc. or by any other owners of third-party trademarks used on this website. LTIAAS is not responsible for the content, quality, or accuracy of any websites linked to or from this website that are not owned by LTIAAS. If you have any questions or concerns about the use of any trademarks or content on this website, please contact us.