CASE 01 — AUTH & INTEGRATION
Someta runs inside Canvas through LTI 1.3, live at two partner institutions. Every launch arrives as a signed token from someone else's server. The job is deciding, in five steps, whether to believe it.
Fig. 1 — OIDC launch sequence
A student clicks the tool inside their course. Canvas posts a login hint — not the student's identity, just a handle Canvas will recognise when we hand it back.
Nothing is authenticated yet. This step exists only so the platform and the tool can agree on who is about to be asked about.
We send the browser back to Canvas's authorize endpoint with a nonce and a signed state. The state is how we later prove the token we receive answers the request we actually made, rather than one someone replayed at us.
The parameters are fixed by the spec: response_type=id_token, response_mode=form_post, and prompt=none — the student is already logged into Canvas, so there is nothing to prompt for.
Canvas posts back an id_token: a JWT it signed with its own private key, carrying the student's identity, their roles, the course context, and the deployment it came from.
At this moment it is bytes from the open internet that claim to be from Canvas. Everything that makes it trustworthy happens next.
Verification pins the algorithm to RS256. A token arriving with alg: none or an HMAC algorithm is the classic way to forge a JWT, and most libraries will accept one unless you forbid it explicitly.
Then the nonce has to match the one recovered from our signed state, and the deployment has to be ours. And the whole thing fails closed: if the tool isn't fully provisioned, it refuses to verify at all, rather than letting an empty deployment id quietly degrade into "accept any deployment".
# LTI requires RS256; never accept "alg": "none" or HMAC. _ALLOWED_ALGORITHMS = ["RS256"] _REQUIRED_CLAIMS = ["exp", "iat", "iss", "aud", "sub"] # An empty deployment_id must NOT degrade into # "accept any deployment". if not config.deployment_id: raise LtiValidationError("not configured")
With the launch verified we mint a session token, and resolve the course and the teacher-or-student role once, baking both into its claims so the client reads them synchronously instead of guessing.
That "once" is load-bearing. The resolved course used to be both stored on the token and recomputed on every read — harmless while both paths read the same globals, and a split brain the moment the default became per-registration. For about a week, 28 unrelated courses read as the wrong subject. It is now a stored field with no default value, so it sits among the required fields and a future code path cannot mint a token with an empty course and still pass every test.
Live at two institutions. I wrote every commit in this module, plus the setup documentation each Canvas administrator needed before they would approve the deployment — because a deployment isn't finished until someone on the other side can operate it without me.