Skip to content

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 editor

Functions ​

validateLtiLaunch() ​

Validate an incoming LTI 1.3 launch request.

javascript
import { validateLtiLaunch } from './lti-auth.js';

const result = validateLtiLaunch(idToken, clientId, deploymentId);

Parameters:

ParameterTypeDescription
idTokenstringJWT from D2L
clientIdstringExpected client ID
deploymentIdstringExpected 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:

ParameterTypeDescription
dataobjectData to encode in token
secretstringSecret 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:

ClaimDescription
subUnique user identifier
emailUser's email address
nameUser's display name
given_nameFirst name
family_nameLast name
https://purl.imsglobal.org/spec/lti/claim/rolesUser roles
https://purl.imsglobal.org/spec/lti/claim/contextCourse context
https://purl.imsglobal.org/spec/lti/claim/customCustom 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:

  1. Signature — Verified against D2L's public key
  2. Issuer — Must match D2L's issuer URL
  3. Audience — Must match our client ID
  4. Expiration — Token must not be expired
  5. 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-secret

Error Handling ​

Common Errors ​

ErrorCauseSolution
Invalid signatureWrong public keyUpdate D2L keys
Token expiredOld launch requestUser should retry
Invalid audienceWrong client IDCheck configuration
Missing claimsIncomplete LTI setupCheck 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
}

Released under the MIT License.