IMS LTI 1.3 based starter (sample) application written using Java and Spring Boot
The goal is to have a Java based web app which can serve as the basis (or starting point) for building a fully compliant LTI 1.3 tool.
###Endpoints
- Target: https://localhost:443/lti3
- OIDC Initiation: https://localhost:443/oidc/login_initiations
- Config: https://localhost:443/config/
- Config Alternative Domains: https://localhost:443/config/altDomain/
- JWKS: https://localhost:443/jwks/jwk
- Dynamic Registration: https://localhost:443/registration
- Dynamic Registration with Alternative Domain: https://localhost:443/registration/{altDomain}
If you do not have Java v16 installed we recommend installing the adoptium version through homebrew
brew tap homebrew/cask-versions
brew install --cask temurin16
You will also need Maven
brew install maven
To run it and connect it to real LMSs it is recommended to run it in an accessible server with a valid certificate. Follow all the setup steps below to set it up.
Things you can do without the certificate:
- Use the config endpoint - https://localhost:443/config/
- See
Accessing endpoints
in theExtra Features and Information
section below on how to configure authorization to access the endpoints
- Install PostgresQL (version 13.4 as of today):
brew install postgres
- Start the database:
brew services start postgres
- Create a Postgres user and database for this project by entering the following in the Terminal:
psql postgres
CREATE ROLE lti13user WITH LOGIN PASSWORD 'yourpassword';
ALTER ROLE lti13user CREATEDB;
\q
psql postgres -U lti13user
CREATE DATABASE lti13middleware;
GRANT ALL PRIVILEGES ON DATABASE lti13middleware TO lti13user;
It is recommended to use a properties file external to the jar to avoid to store sensitive values in your code.
- Copy the
application.properties
insidesrc/main/resources
and name the copyapplication-local.properties
- This is the
.properties
file where we will edit values for local development as described below - Ensure that the values in
application-local.properties
match the database name, user, and password that you used when creating the postgres database in the previous section.
Alternatively, you can create an application.properties
file in a config
folder in the root of the project, and override any property for local development.
- cd to the directory that you want the keys to be located
openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
cat publickey.crt
- Copy this value to
oidc.publickey
inside the.properties
file. The key needs to all be on one line, with a newline character,\n
, preceding and following the string that you copy in between---BEGIN PUBLIC KEY---
and---END PUBLIC KEY---
. openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key
cat pkcs8.key
- Copy this value to
oidc.privatekey
inside the.properties
file. The key needs to all be on one line, with a newline character,\n
, preceding and following the string that you copy in between---BEGIN PRIVATE KEY---
and---END PRIVATE KEY---
.
Note: As of 8/11/2021, for Mac, this works with Firefox but not Chrome.
- from the middleware directory, cd into
src/main/resources
- Generate ssl certificate in the resources directory:
keytool -genkeypair -alias keystoreAlias -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650
- Enter any values desired when prompted, but make sure you remember the password that you used.
- In the
.properties
file, ensure that each of these variables have the correct values filled in:
server.port=443
security.require-ssl=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=<password from step 2>
server.ssl.keyStoreType=PKCS12
server.ssl.keyAlias=keystoreAlias
security.headers.frame=false
application.url=https://localhost:443
- After running the spring boot application, you should be able to reach it at https://localhost:443
- Download and install ngrok
- You will need to set up an account to use localhost. See the setup instructions
./ngrok http 443
(Note: This port number must match the value ofserver.port
in the.properties
file.)- Ensure in your
.properties
file that theapplication.url
is set to thehttps
ngrok url. eg:application.url=https://a50a-97-120-101-43.ngrok.io
- Utilize the https url from ngrok when registering your tool with the LMS platform
Note: Each time you restart ngrok, you will need to change the url of your tool in your registration with the LMS. However, you may restart the tool as much as you like while leaving ngrok running without issue.
Start by building the .jar
file
mvn clean install
The following command below will run the app with the application-local.properties
file assuming you placed it in the project's resources directory.
Find the name of the .jar file in the target directory and replace "name-of-.jar"
with your .jar
java -jar target/"name-of-.jar" --spring.config.name=application-local
You can run the app in place to try it out without having to install and deploy a servlet container.
mvn clean install spring-boot:run -Dspring-boot.run.arguments=--spring.config.name=application-local
You can add this -DskipTests=true
to either of the build and run command to skip running the tests
- This can be useful if you know the tests are passing and want to save time in development as running the tests can add on extra build time.
mvn clean install -DskipTests=true
mvn clean install -DskipTests=true spring-boot:run -Dspring-boot.run.arguments=--spring.config.name=application-local
- To set up access to endpoints you will need authorization stored in the
.properties
fileterracotta.admin.user
terracotta.admin.password
- Likely the main endpoints you will want to test or use directly when developing locally is the
/config/
endpoint - This endpoint is how we access and save LTI 1.3 tool deployment records which need to be configured when setting up a tool in an LMS
- To add alternative domain configurations the path to the endpoint is
/config/altDomain
- It supports GET, POST, and adding the altDomain id to the path, GET (one), PUT and DELETE
- The structure of the altDomain is:
"altDomain": "sunymar",
"name": "Lti tool name Suny Playa",
"description": "Desired description of the tool",
"menuLabel": "Menu Label",
"localUrl": "https://lti-sunymar.one.lumenlearning.com",
"domainUrl": "https://home-sunymar.one.lumenlearning.com"
}
Only altDomain and name can't be null.
altDomain
needs to be unique because it will be the id, used later to get or put or delete
name
must be unique too.
description
and menuLabel
will take the default values if they are empty or null.
If localURL
or domainURL
are empty, they will be calculated based on the default baseUrl and domainURL values.
- There are Postman exports stored in
src/test/postman
that can be imported in to Postman to use the config endpoints. - Once imported, in Postman:
- Add the value you have set for the
terracotta.admin.password
to theauth_password
variable stored in thelti-middleware
Globals in Environments Section of Postman
- Add the value you have set for the
In the .properties
file, the following env variables have been added.
lti13.demoMode=false
lti13.enableAGS=true
lti13.enableMembership=false
lti13.enableDynamicRegistration=false
lti13.enableDeepLinking=false
lti13.enableRoleTesting=false
lti13.enableTokenController=false
lti13.enableMockValkyrie=false
lti13.demoMode
must have a value oftrue
for AGS, Membership, or Deep Linking (the LTI Advantage Services) to function regardless of the value of their individual env variables. This is because each of these services is currently a demo.lti13.enableAGS
must have a value oftrue
in addition tolti13.demoMode
having a value oftrue
for the Assignments and Grades (AGS) demo service to function.lti13.enableMembership
must have a value oftrue
in addition tolti13.demoMode
having a value oftrue
for the Membership demo service to function.lti13.enableDeepLinking
must have a value oftrue
in addition tolti13.demoMode
having a value oftrue
for the Deep Linking demo service to function.lti13.enableMockValkyrie
must have a value oftrue
in order to do integration testing locally.- The remaining new env vars
lti13.enableDynamicRegistration
,lti13.enableRoleTesting
,lti13.enableTokenController
are for test endpoints.lti13.enableDynamicRegistration
corresponds to theRegistrationController
which has not been tested.lti13.enableRoleTesting
corresponds to theTestController
which I suspect will be deleted as role authorization is not needed.lti13.enableTokenController
corresponds toTokenController
which I suspect will be deleted if it remains unused.
- The authentication to AWS uses Spring's default authentication chain.
- The URI for a SQS queue should be filled in for either
lti13.grade-passback-queue
or asLTI13_GRADE_PASSBACK_QUEUE
environment variable. - The region of the SQS queue should be filled in for either
cloud.aws.region.static
in the.properties
file orAWS_REGION
as an environment variable.
Prior to running mvn clean install spring-boot:run
, do the following:
- Ensure that the following property is present and uncommented in the
.properties
file:spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,org.springframework.cloud.aws.autoconfigure.context.ContextCredentialsAutoConfiguration,org.springframework.cloud.aws.autoconfigure.context.ContextInstanceDataAutoConfiguration,org.springframework.cloud.aws.autoconfigure.context.ContextRegionProviderAutoConfiguration,org.springframework.cloud.aws.autoconfigure.context.ContextResourceLoaderAutoConfiguration,org.springframework.cloud.aws.autoconfigure.context.ContextStackAutoConfiguration
- Set the Spring Boot profile to
no-aws
in the run command by doing one of the following:java -jar target/"name-of-.jar" --spring.config.name=application-local --spring.profiles.active=no-aws
mvn clean install -Dspring-boot.run.profiles=no-aws spring-boot:run -Dspring-boot.run.arguments=--spring.config.name=application-local
brew install envchain
envchain --set NAMEYOURAWSENV SPACE_DELIMITED_VARIABLES
For example:
envchain --set aws-sqs-gpb AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY
- Enter the values of these variables that correspond to the AWS account for your SQS queue for grade passback.
- Confirm the variables are set by running:
envchain aws-sqs-gpb env | grep AWS_
- Ensure that the following properties are set in the
.properties
file:
cloud.aws.region.static=us-west-1
cloud.aws.region.auto=false
cloud.aws.stack.auto=false
lti13.grade-passback-queue=<queue name>
- Ensure that the
spring.autoconfigure.exclude
property is commented out in the.properties
file. - Run the application, ensuring that the no-aws profile is not being set.
{
"Type" : "Notification",
"MessageId" : "f85561b6-dd51-5de6-8d75-3b128d9598d5",
"TopicArn" : "arn:aws:sns:us-west-2:725843923591:develop-lti13_grade",
"Message" : "{\"user_id\":\"4f3d12df-e1ae-484f-8b9a-b667864e8100\",\"lineitem_url\":\"https://canvas.unicon.net/api/lti/courses/3348/line_items/496\",\"client_id\":\"97140000000000230\",\"deployment_id\":\"523:0a47de91cf84ee147f6e534195988508504d3e82\",\"issuer\":\"https://canvas.instructure.com\",\"score\":0.42857142857142855}",
"Timestamp" : "2022-03-10T19:16:20.157Z",
"SignatureVersion" : "1",
"Signature" : "j48wQktGN/UUjwsUK+w7tpn68ANS7/nM8Kwbb848sEZ1Hl7+N1/D8CoDutFHzaT2GbQ+Vh6NFCXtEMyX2LF/UwqqTRqLYknIm7NVCob9/2hADuE6Ix5mPcJKD+Y7nwl6GGu/6I9o+JzVzO2DqOVz7rL3QGnZrJi35RU/SGAxE1NcKl24y6bR+kGPK6O+O3WlyFIPkAP74qIgqdVGtq6v7OJtsDirxBg0JrX4eRNTVMWT+1CK/n78yG8EWs9SARHaLtN+donriYuw59G7ofkwKVmjYniJixpz1hIsyTzP/TZpi+Fn8ZobYMB5WsuUkjnqZTsV45Q/Wm14Ul8rMvUYEg==",
"SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem",
"UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:725843923591:develop-lti13_grade:e4402d2e-ab0c-4fdf-b3a7-dc0d745466e5"
}
When there's a deep linking request the LTI tool displays a Harmony course selector to the instructor, the list of courses comes from a Harmony endpoint.
Please complete this properties in the .properties
harmony.courses.api=http://localhost:3000/lti/deep_linking
To do integration testing locally ensure that lti13.enableMockValkyrie=true
and set the harmony.courses.api
equal to your ngrok url with /valkyrie
appended to the end. (e.g. https://5f41-184-101-29-219.ngrok.io/valkyrie
)
If using dynamic registration, ensure that the domain.url
in the .properties
file is set to the appropriate version of Waymaker. ie:
domain.url=https://local.waymaker.xyz
Adding an altDomain path parameter in the registration url will register the tool with alternative name
, localURL
and domainURL
and optionally description
and menuLabel
The configuration property allow.flexible.urls
with a default value of false, if set to true will allow urls like following this pattern *.application.url
and *.home.url
to be accepted meanwhile there is an entry in the configuration database for the value that * represents.
It will use the altDomain attribute during the Dynamic Registration to allow custom dynamic registration urls.
- Push changes to the branch that you want to push to the dev environment (e.g. L3-66-integration-testing)
git branch -d dev-env
git checkout -b dev-env
git pull origin L3-66-integration-testing
git push --force --set-upstream origin dev-env
- Wait 10-15 minutes for the deployment to complete.
- View the logs using
aws logs tail /aws/elasticbeanstalk/lti-service-dev-docker/var/log/eb-docker/containers/eb-current-app/stdouterr.log --follow --since 1h
The Deep Linking UI was written in React to support deep linking messages in the LTI middleware, it allows the instructor to pick courses from Waymaker.
It can be displayed in the LMS launching the Middleware from a deep link request or it can be run independently using NPM.
You can install NPM chosing your OS favorite distribution from here.
We recommend installing the same Node.JS and NPM versions described in the pom.xml to be consistent with the build process.
In the 'src/main/frontend' directory, you can run:
Runs the app in the development mode.
Open http://localhost:3000 to view it in your browser.
The page will reload when you make changes.
You may also see any lint errors in the console.
Launches the test runner in the interactive watch mode.
See the section about running tests for more information.
The project contains the frontend-maven-plugin
plugin that downloads/installs Node and NPM locally, runs npm install, and then any combination of Bower, Grunt, Gulp, Jspm, Karma, or Webpack. It's supposed to work on Windows, OS X and Linux.
After building and packing the entire React UI, it deploys it together with the Thymeleaf existing static files.