Authenticating API Requests
Traffic flows both ways in a Connect integration, and each direction is secured differently. Both matter: getting the second one wrong means anyone who finds your Service URL can write grades into your gradebook.
Calling LTIAAS
Every endpoint you call takes your account API key as a bearer token:
Authorization: Bearer <API_KEY>
For example:
Authorization: Bearer df06d55e-3b0f-4121-b60f-c39469b5b550
const headers = { Authorization: `Bearer ${LTIAAS_API_KEY}` }
const { data } = await axios.post(`${LTIAAS_URL}/api/launch/core/form`, body, { headers })
The key is in the LTIAAS Portal under API Settings. It is the same key for /api and /admin endpoints.
LTIAAS API endpoints must only be called from your back-end. Every request carries your API key, so a call from the browser leaks full control of your account. Keep the key in your server's environment.
Failures
A missing or malformed header, or the wrong key, returns 401:
{
"status": 401,
"error": "Unauthorized",
"details": {
"message": "INVALID_BEARER_AUTHORIZATION_HEADER",
"description": "Invalid API Key for account."
}
}
A valid key on the wrong kind of account returns 403 INVALID_ACCOUNT_TYPE — this is a Launch account being used against Connect endpoints, or the reverse.
Verifying Requests from LTIAAS
LTIAAS calls your server at three points: your Launch URL, your Deep Linking URL, and your Service URL. Each carries a signed JWT, and you must verify it before acting on the contents.
The token is signed RS256 with your account's private key. Verify it with the consumer public key from the portal.
const jwt = require('jsonwebtoken')
// From the portal, stored as an environment variable.
const LTIAAS_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
${process.env.LTIAAS_PUBLIC_KEY}
-----END PUBLIC KEY-----`
let decoded
try {
decoded = jwt.verify(payload, LTIAAS_PUBLIC_KEY)
} catch (err) {
return res.status(401).send('Invalid payload')
}
Never decode without verifying. jwt.decode() reads a token without checking its signature — anyone could then forge a launch as any user, or post grades for any learner. Always jwt.verify().
Where the Token Arrives
| Endpoint | How it arrives |
|---|---|
| Launch URL | GET, as the payload query parameter |
| Deep Linking URL | GET, as the payload query parameter |
| Service URL | POST, as a payload field in a JSON body |
Service URL, for DEEP_LINKING_RESPONSE | POST, as a payload field in a form-encoded body |
What Is Inside
A launch token:
{
"type": "CORE_LOGIN",
"metadata": "kR2mQ9dLxTn4vB8sYw1e",
"parameters": {
"user": "rKk4PdLgcRbqE4PdSW3iV0KhAmu2",
"context": "0001",
"resource": "14113"
},
"iat": 1700000000,
"exp": 1700000600
}
type—CORE_LOGINorDEEP_LINKING_LOGINat your launch URLs; one of the nine service request types at your Service URL.metadata— pass this straight back on the matching ID Token call. Only present on launch tokens.parameters— the identifiers you supplied when starting the launch, or the details of the service request.
Expiry
Tokens are valid for ten minutes. jwt.verify() enforces this for you, which is another reason not to hand-roll the check.
The metadata value expires on the same ten-minute clock. Anything slow between receiving the payload and completing the launch — a login prompt, a consent screen — will push you past it. Authenticate users before starting a launch.
A Note on the Other Keys
https://your.ltiaas.com/lti/keys also serves public keys, and they are not the ones on this page. That endpoint publishes a per-tool key that tools use to verify the ID Tokens LTIAAS signs on your behalf. It has nothing to do with verifying requests sent to you.
- Consumer public key (portal) → you verify LTIAAS's requests to you.
- Keyset (
/lti/keys) → tools verify LTIAAS's tokens to them.
Next Steps
Error handling — the shape of failures and what the common codes mean.
