Core Launch
This is the flow every Connect integration needs. It takes a user from clicking an activity in your LMS to landing inside a third-party tool, signed in, in the right course, with the right role.
The code below is Node, but nothing here is framework-specific — it is two HTTP calls and one JWT verification.
This walkthrough assumes you have configured your account and have a clientId from registering a tool.
What You Need on Hand
const LTIAAS_URL = 'https://your.ltiaas.com'
const LTIAAS_API_KEY = process.env.LTIAAS_API_KEY
const LTIAAS_PUBLIC_KEY = process.env.LTIAAS_PUBLIC_KEY // from the portal
Step 1 — Start the Launch
Your front-end asks your back-end to open an activity. Send whatever identifies the activity in your system; the back-end turns it into a launch.
// Front-end
async function openActivity(courseId, activityId) {
const response = await fetch('/api/lti/launch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ courseId, activityId })
})
const { form } = await response.json()
// Render the returned form in an iframe. It submits itself on load.
document.getElementById('toolContainer').innerHTML =
`<iframe title="Learning tool" width="100%" height="600" frameborder="0"
src="data:text/html,${encodeURIComponent(form)}"></iframe>`
}
Your back-end looks up the activity, then calls LTIAAS:
// Back-end — POST /api/lti/launch
app.post('/api/lti/launch', requireLogin, async (req, res) => {
const { courseId, activityId } = req.body
// Only your LMS can answer these questions.
const activity = await db.activities.find(activityId)
await assertUserCanAccess(req.user, courseId)
const { data } = await axios.post(
`${LTIAAS_URL}/api/launch/core/form`,
{
clientId: activity.toolClientId,
context: courseId,
resource: activityId,
user: req.user.id
},
{ headers: { Authorization: `Bearer ${LTIAAS_API_KEY}` } }
)
res.json({ form: data.form })
})
This call carries your API key, so it belongs on the back-end. If the front-end called LTIAAS directly, anyone could read the key out of the network tab and launch anything as anyone.
Optional Fields
| Field | Use it to |
|---|---|
launchEndpoint | Point this launch at a specific URL instead of the tool's default. Deep-linked content needs this. |
personalData | Override the tool's privacy level for this launch only. |
customParameters | Send extra key/value pairs. Merged over the tool's registered parameters, so yours win. |
context, resource and user are your identifiers, passed through untouched. Use your real database IDs — you will be looking them up again in step 3.
Step 2 — LTIAAS and the Tool Shake Hands
The iframe loads, the form submits itself into the tool, and the tool begins the OIDC handshake against your authentication endpoint. LTIAAS validates it all.
Nothing of yours runs here. Move on.
Step 3 — Verify the Payload and Identify the User
LTIAAS now redirects the iframe to your Launch URL with a signed token:
GET https://yourlms.com/lti-validate?payload=<JWT>
Verify it, then answer the question it is really asking: who is this, and what are they opening?
const jwt = require('jsonwebtoken')
app.get('/lti-validate', async (req, res) => {
let decoded
try {
decoded = jwt.verify(req.query.payload, LTIAAS_PUBLIC_KEY)
} catch (err) {
return res.status(401).send('Invalid launch token')
}
// The identifiers you sent in step 1 come back here.
const { user: userId, context: courseId, resource: activityId } = decoded.parameters
const user = await db.users.find(userId)
const course = await db.courses.find(courseId)
const activity = await db.activities.find(activityId)
// Map your permission model onto LTI® roles.
const roles = user.isTeacherOf(course) ? ['CONTEXT_INSTRUCTOR'] : ['CONTEXT_LEARNER']
// ... continue to step 4
})
The decoded payload looks like this:
{
"type": "CORE_LOGIN",
"metadata": "kR2mQ9dLxTn4vB8sYw1e",
"parameters": {
"user": "rKk4PdLgcRbqE4PdSW3iV0KhAmu2",
"context": "0001",
"resource": "14113"
},
"iat": 1700000000,
"exp": 1700000600
}
metadata expires ten minutes after the launch started. Do not put a login screen or a consent dialog in this step — by the time the user finishes, the launch is dead. Authenticate before step 1.
Step 4 — Complete the Launch
Send the user, context and resource back to LTIAAS. It signs an ID Token and returns a form; hand that form to the browser and the user is in the tool.
const { data } = await axios.post(
`${LTIAAS_URL}/api/idtoken/core/form`,
{
metadata: decoded.metadata,
user: {
id: user.id,
name: user.name,
email: user.email,
givenName: user.firstName,
familyName: user.lastName,
roles
},
context: {
id: course.id,
label: course.code, // "CS101"
title: course.name, // "Computer Science 101"
type: ['CourseOffering']
},
resource: {
id: activity.id,
title: activity.name,
description: activity.description
}
},
{ headers: { Authorization: `Bearer ${LTIAAS_API_KEY}` } }
)
res.send(data.form) // self-submitting; lands the user in the tool
})
context and resource were plain strings in step 1 and are objects here. Step 1 routes the launch; step 4 describes it.
About Roles
roles takes LTIAAS role keys, not the full IMS URLs — LTIAAS expands them for you. Most integrations only ever need two:
CONTEXT_INSTRUCTORfor teachersCONTEXT_LEARNERfor students
Tools commonly change what they show based on this, so getting it right matters more than it looks. The full list covers system, institution and context roles.
About Personal Data
Send complete user records. LTIAAS strips whatever the tool's privacy level does not permit, so a NONE tool sees no name or email even though you sent both. You do not need to branch on it yourself.
Testing It
- Click an activity. The iframe should fill with the tool, with no visible redirects.
- If it stays blank, open the iframe in its own tab — errors from LTIAAS are JSON and easy to read once they are not hidden inside a frame.
403 INACTIVE_TOOLmeans the tool is deactivated.404 UNREGISTERED_TOOLmeans theclientIdis wrong.400 INVALID_METADATA_PARAMETERin step 4 means more than ten minutes passed since step 1.
More on failures in Error handling.
Next Steps
- Deep linking — let teachers pick which content to add.
- Handling service requests — answer roster reads and grade writes.
