Skip to main content

The LTI® 1.3 Security Model

An LTI® launch arrives as an HTTP request from a browser, claiming to come from a university. Everything in it is attacker-controllable until something proves otherwise. This is what does the proving, and which parts of it are easy to get wrong.

The rules come from the LTI® 1.3 specification and 1EdTech's security framework, built on JSON Web Key and OAuth 2.0.

What the model has to defend against

Four things, and they are worth naming because each maps to a specific check:

  1. Forgery. Anyone can POST to your launch endpoint claiming to be an institution's LMS.
  2. Replay. A launch captured once — from a log, a proxy, a shared machine — should not work a second time.
  3. Misdirection. A token minted for one tool should not be accepted by another.
  4. Staleness. A launch from last month should not still open a session.

How it defends against them

Forgery: signatures over shared secrets

The platform signs the ID Token with a private key it never discloses. The tool verifies with the matching public key, fetched from the platform's published key set. The token header names a kid, which selects the key.

This is the change from LTI® 1.1, where both sides held the same secret. A shared secret has to be transported to be shared, is rarely rotated because rotating needs both sides to act together, and if leaked lets an attacker mint launches against every tool it was shared with. A public key is worth nothing to an attacker.

Two ways this goes wrong in practice: fetching the key set over a connection you do not validate, and caching a key set forever so a legitimate rotation looks like an attack — or never caching, and being rate-limited into an outage.

Replay: nonce and state

Every launch carries a nonce. The tool records it and rejects a repeat. The window only has to cover the token's lifetime, but within that window a duplicate has to fail.

state is separate and does a different job: the tool generates it, sends it in the authentication request, and checks it matches on the way back. That is what ties the response to a request this tool actually made, rather than one an attacker initiated.

Both are easy to skip because nothing appears broken when you do. A launch without nonce checking works perfectly, right up until someone replays one.

Misdirection: the audience claim

aud names the client the token was minted for. A tool that does not check it will happily accept a token issued for a different tool by the same platform. iss likewise has to match the registration the tool thinks it is handling — and because a tool may hold many registrations, "match the registration" means looking up by issuer and client ID together, not just recognising the issuer.

Staleness: expiry

exp is short by design. Check it, and do not add tolerance beyond a small allowance for clock skew.

Clock skew is worth its own note because it produces a confusing failure: if the platform's server clock has drifted forward, a token can look as though it was issued in the future and be rejected as invalid. The message usually blames the token rather than the clock. See the error message reference.

The service calls are a separate model

Verifying a launch does not authorise a tool to call anything. The LTI® Advantage services use OAuth 2.0 client credentials: the tool signs a JWT assertion with its own private key, exchanges it at the platform's token endpoint for an access token scoped to specific permissions, and calls the service with that.

So a complete implementation holds a key pair of its own, publishes a key set, and manages token lifetimes — a second set of key handling on top of the first.

Scopes are granted per registration and per course. A tool should treat the absence of a service as normal rather than an error, because a platform can grant grade access in one course and withhold it in another.

Personal data

The specification does not require a platform to send a name or an email address, and many are configured not to. Treat identity as the issuer plus the sub claim, and everything else as optional.

Two consequences worth designing for. An integration that needs an email address to create an account will fail at institutions that send none. And matching users by email address — tempting, because it appears to solve account linking — is unsafe: addresses are reused, changed, and shared, and the same address at two institutions is not necessarily the same person.

The verification checklist

If you implement LTI® yourself, every one of these has to happen before the token is trusted:

  • Signature verifies against the platform's key set, using the kid from the header
  • Key set is fetched over a validated connection, cached, and re-fetched on an unknown kid
  • iss and aud together match a known registration
  • exp has not passed, with only a small skew allowance
  • nonce has not been seen before, and is stored long enough to matter
  • state matches the value this tool issued
  • Message type and version claims are ones you support
  • deployment_id is one you recognise for that registration

Missing any single one leaves a working integration with a hole in it, which is the difficulty: nothing fails visibly.

If you use LTIAAS, these are performed before your application is involved — your endpoint receives a launch that has already been verified, and the ltik it arrives with is what proves that to your back end. That does not remove your own obligations: session handling, account linking and authorisation inside your product remain yours.

Common questions

Is LTI® 1.3 secure?

The specification is sound — signed tokens, published keys, replay protection. Whether a given integration is secure depends on whether it performs every verification step, and skipping one is easy to do without anything appearing broken.

What stops someone forging an LTI® launch?

The signature. The ID Token is signed with the platform's private key and verified against its published key set, so a forged token fails verification. That protection only exists if the tool actually checks the signature and the issuer.

Do I need to store nonces?

Yes, if you implement LTI® yourself. The nonce is what stops a captured launch being replayed. It needs to be stored long enough to cover the token's lifetime and rejected if seen twice.

Does LTI® send personal data?

It can, and the platform decides how much. Many are configured to send no name or email, so an integration that requires them will fail at some institutions.

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.