All postsBuilding for US school districts · Part 7

SSO for schools: LTI 1.3, Clever, Google and one student

How a child actually signs in, why the same student arrives through four different doors, and what your tool has to do when the launch carries no name at all.

Rostering tells you who exists. Authentication decides how they get in, and in a school those are genuinely different problems, because the same child arrives through different doors on different days and each door hands you a different identifier.

Get this wrong and the symptom isn't a login error. It's a student who did their work on Monday and can't find it on Tuesday, reported by a teacher as data loss.

Illustration of four different doors on different walls all opening into the same small room, representing multiple sign-in routes resolving to one student record
Four doors, four keys, one room. The whole job is knowing they lead to the same place.

The doors

  • Clever Instant Login. OAuth 2.0. A student clicks your tile in the Clever portal and lands authenticated.
  • ClassLink OneClick. The same shape, from the LaunchPad portal.
  • LTI 1.3 from an LMS. A launch from inside Canvas, Schoology, Moodle, Blackboard or Google Classroom, carrying a signed JWT.
  • Google Workspace for Education. Sign in with Google on a managed Chromebook.
  • Microsoft Entra ID. The same, in Microsoft districts.
  • SAML 2.0. Older, still present, particularly in larger districts and state systems.
  • Username and password. Still required, because substitute teachers exist, because a portal goes down on a Tuesday, and because some districts run none of the above.

Anyone serving more than a handful of districts supports most of that list at once. That isn't architectural excess. It's the market.

LTI 1.3, concretely

Worth understanding properly, because it's both the most common LMS route and the most misimplemented.

The flow is OpenID Connect with a third-party-initiated login. The platform sends the browser to your login endpoint with iss, login_hint and target_link_uri. You redirect back to the platform's authorisation endpoint. The platform posts an id_token, a JWT signed with a key from its JWKS, to your launch endpoint. You validate it and the user is in.

Validating properly means verifying the signature against the platform's published JWKS, checking iss, aud, exp and iat, and checking the nonce for replay. That last one gets skipped surprisingly often.

The deployment_id trap

Your tenancy key is not iss alone. It's the triple iss + client_id + deployment_id.

A single platform issuer can host many deployments: several districts on the same regional Canvas instance, or several sub-accounts inside one district. Key your tenant on iss alone and two districts sharing an LMS host share a tenant.

That's a data isolation failure, not a bug. It gets discovered during a security review if you're lucky, and by a district if you aren't.

Roles are contextual and plural

roles is an array and a user can hold several. Someone can be an Instructor in one context and a Learner in another. Authorise on the context role for that launch, and never cache "this person is a teacher" globally from one launch.

Role URIs also come in institution and membership flavours, and platforms vary in which they send. Match on the suffix, handle both, and define a behaviour for a role you don't recognise. It should be the least privileged one.

The launch may be anonymous

This surprises people most.

District policy, expressed in the LMS as a privacy level, can strip the name and email claims from the launch. You receive a sub and nothing else identifying. The tool still has to run, still has to store the work, and still has to attach it to the right person on the way back.

The design that survives: the sub claim, scoped to the deployment, is your identifier. Names are display decoration you may or may not have.

If your user creation path requires a display name, an anonymous launch either fails or creates "Unknown User" a thousand times. Make name nullable, render a fallback, and if you need names, get them from NRPS rather than from the launch.

NRPS and AGS

Two LTI Advantage services worth wiring from the start.

Names and Role Provisioning Services fetches the membership of a context on demand: the current roster of that course, from the platform, right now. This is the freshest roster data available anywhere, and it powers the per-class re-sync described in a roster sync that survives the school year.

Assignment and Grade Services is score passback, covered in grade passback that lands.

Both use OAuth 2.0 client credentials with a signed JWT assertion, scoped per service. Cache the tokens. They're short-lived and platforms rate-limit.

The Google problem

If your product runs on Chromebooks, and in US K-12 it very likely does, there's an operational fact worth knowing before it costs you a deployment.

In Google Workspace for Education, an administrator controls which third-party applications may access Google accounts, by OAuth client id. For users under 18, access to applications that haven't been explicitly configured is blocked by default.

So your "Sign in with Google" button can work perfectly in your own testing and fail for every student in a district, with an error message the teacher can't interpret, until an administrator allowlists your client id.

Handle it in three places:

  1. Document the client id and required scopes somewhere a district admin can find in thirty seconds.
  2. Detect that specific failure and render an explanation aimed at the administrator rather than at the child.
  3. Request the narrowest scopes you can. A request for broad Drive access is a question you'll answer during the privacy review.

Resolving the fan-out

All of this converges on one requirement: several external identifiers resolving to one internal user.

identities (provider, external_id, user_id, verified_at)

Where provider is clever, classlink, lti:{iss}:{deployment_id}, google, oneroster:{district} and so on.

Match in this order of preference:

  1. An identifier you've already seen. Exact match, no ambiguity.
  2. A cross-reference the source gives you. Clever exposes the SIS id alongside its own. An LTI launch can carry a district-configured custom parameter for exactly this. Ask districts to configure it. Many will.
  3. A deliberate, logged match on a stable attribute. School-scoped student number, or email where it exists.

One rule about what not to do. Never auto-match on name plus date of birth. Two students in a district of forty thousand share a name and a birthday more often than intuition suggests, and merging two children's records is a serious incident, not a data quality issue. Where a match is ambiguous, build a review queue and let a human decide.

Sessions, shared devices and the bell

Two classroom realities that belong in the authentication design.

Devices are shared. A cart of Chromebooks serves four classes a day. Session lifetimes tuned for a personal laptop mean the next child inherits the previous child's session. Bind sessions tightly, log out on browser close by default, and make "not you?" an obvious control on every screen.

Nine minutes between bells. Any authentication friction gets measured in lost instructional minutes and reported to your CTO as a complaint. Second factors for students are almost always the wrong call. Second factors for staff with administrative access are almost always the right one. Distinguish them.

Common questions

What is LTI 1.3?
A 1EdTech standard for launching an external tool from a learning management system. It uses OpenID Connect with a third-party initiated login and a signed JWT, and its LTI Advantage services add Deep Linking, Names and Role Provisioning, and Assignment and Grade Services.
Why does Sign in with Google fail for students in a district?
Because a Google Workspace for Education administrator controls which third-party applications may access accounts, by OAuth client id. For users under 18, applications that have not been explicitly configured are blocked by default until an administrator allowlists your client id.
Can an LTI launch arrive with no name or email?
Yes. District policy, expressed as a privacy level in the LMS, can strip name and email claims from the launch. Your tool receives only a subject identifier and must still run and still attach work to the right person, so name and email must be nullable throughout.

Working on something that has to survive a district?