LTI Authentication
Documentation for the LTI 1.3 authentication module.
Overview
The LTI (Learning Tools Interoperability) module handles secure authentication between D2L Brightspace and the Gradescope Submitter.
LTI 1.3 Flow
D2L Brightspace
│
├─── 1. User clicks LTI link
│
▼
POST /lti/login (OIDC initiation)
│
├─── 2. Validate request
├─── 3. Generate state token
│
▼
Redirect to D2L authorization
│
├─── 4. D2L authenticates user
│
▼
POST /lti/launch (ID token)
│
├─── 5. Validate JWT signature
├─── 6. Extract user claims
├─── 7. Create session
│
▼
Redirect to code editorFunctions
validateLtiLaunch()
Validate an incoming LTI 1.3 launch request.
javascript
import { validateLtiLaunch } from './lti-auth.js';
const result = validateLtiLaunch(idToken, clientId, deploymentId);Parameters:
| Parameter | Type | Description |
|---|---|---|
idToken | string | JWT from D2L |
clientId | string | Expected client ID |
deploymentId | string | Expected deployment ID |
Returns:
javascript
{
valid: true,
claims: {
sub: "user-id",
email: "student@school.edu",
name: "Student Name",
// ... other claims
}
}generateStateToken()
Create a signed state token for the auth flow.
javascript
import { generateStateToken } from './lti-auth.js';
const state = generateStateToken(
{ email: 'student@school.edu', asgId: '6617143' },
process.env.SECRET
);Parameters:
| Parameter | Type | Description |
|---|---|---|
data | object | Data to encode in token |
secret | string | Secret key for signing |
Returns: Signed JWT string
verifyStateToken()
Verify and decode a state token.
javascript
import { verifyStateToken } from './lti-auth.js';
const data = verifyStateToken(state, process.env.SECRET);
// { email: 'student@school.edu', asgId: '6617143' }storeStudentSession()
Store a student's Gradescope credentials.
javascript
import { storeStudentSession } from './lti-auth.js';
storeStudentSession(userId, {
token: 'gradescope-token',
cookie: 'gradescope-cookie'
});getStudentSession()
Retrieve a student's stored credentials.
javascript
import { getStudentSession } from './lti-auth.js';
const session = getStudentSession(userId);
// { token: '...', cookie: '...' }LTI Claims
Standard LTI 1.3 claims extracted from the ID token:
| Claim | Description |
|---|---|
sub | Unique user identifier |
email | User's email address |
name | User's display name |
given_name | First name |
family_name | Last name |
https://purl.imsglobal.org/spec/lti/claim/roles | User roles |
https://purl.imsglobal.org/spec/lti/claim/context | Course context |
https://purl.imsglobal.org/spec/lti/claim/custom | Custom parameters |
Custom Parameters
Custom parameters passed from D2L:
javascript
const custom = claims['https://purl.imsglobal.org/spec/lti/claim/custom'];
// {
// asg_id: '6617143',
// file_name: 'program.py',
// starter: 'lab1_starter.py'
// }Security
JWT Validation
The ID token is validated for:
- Signature — Verified against D2L's public key
- Issuer — Must match D2L's issuer URL
- Audience — Must match our client ID
- Expiration — Token must not be expired
- Nonce — Prevents replay attacks
State Token Security
State tokens:
- Are signed with HMAC-SHA256
- Include expiration time
- Are single-use (should be invalidated after use)
Configuration
Required environment variables:
env
LTI_CLIENT_ID=your-client-id
LTI_DEPLOYMENT_ID=your-deployment-id
SECRET=your-signing-secretError Handling
Common Errors
| Error | Cause | Solution |
|---|---|---|
| Invalid signature | Wrong public key | Update D2L keys |
| Token expired | Old launch request | User should retry |
| Invalid audience | Wrong client ID | Check configuration |
| Missing claims | Incomplete LTI setup | Check D2L config |
Example
javascript
try {
const result = validateLtiLaunch(idToken, clientId, deploymentId);
if (!result.valid) {
return res.status(401).json({ error: result.error });
}
// Continue with valid launch
} catch (error) {
return res.status(500).json({ error: 'LTI validation failed' });
}Testing
Mock LTI Launch
For testing without D2L:
javascript
const mockClaims = {
sub: 'test-user-123',
email: 'test@school.edu',
name: 'Test Student',
'https://purl.imsglobal.org/spec/lti/claim/custom': {
asg_id: '6617143'
}
};Bypass in Development
javascript
if (process.env.NODE_ENV === 'development') {
// Skip LTI validation, use mock data
}