Skip to main content

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.

info

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 })
})
caution

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

FieldUse it to
launchEndpointPoint this launch at a specific URL instead of the tool's default. Deep-linked content needs this.
personalDataOverride the tool's privacy level for this launch only.
customParametersSend extra key/value pairs. Merged over the tool's registered parameters, so yours win.
note

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
}
caution

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
})
note

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_INSTRUCTOR for teachers
  • CONTEXT_LEARNER for 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

  1. Click an activity. The iframe should fill with the tool, with no visible redirects.
  2. 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.
  3. 403 INACTIVE_TOOL means the tool is deactivated. 404 UNREGISTERED_TOOL means the clientId is wrong.
  4. 400 INVALID_METADATA_PARAMETER in step 4 means more than ten minutes passed since step 1.

More on failures in Error handling.

Next Steps

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.