Safari blocks all 3rd-party cookies by default, which breaks LTI tools that rely on setting cookies when launched in an iframe. Other browsers will soon follow suit. Instead, Safari exposes a new API for getting user-permitted access to set cookies from an iframe, which unfortunately doesn't completely work for LTI use cases. See this article for a detailed explanation of the Storage Access API and previous attempts to launch LTI tools in Safari:

The current workaround that this gem implements is to relaunch the tool in a new tab, new window, or popup window, where it can set first-party cookies to its heart's content. The relaunch occurs during the login request, so the tool is entirely in control, and no other parts of the LTI launch flow are modified.


Add this line to your application's Gemfile:

# Set 3rd party cookies in Safari
gem 'canvas_lti_third_party_cookies'

And then execute:

$ bundle install

Since this gem includes some static assets (JS, CSS, images), you will need to make sure that your application runs the rake assets:precompile task at some point during its build process. Most Rails applications will be running that already, either directly or as part of the rake webpacker:compile task.

Neglecting to precompile assets will result in runtime errors when users try to launch your LTI tool.


Choose the Rails controller action that's used to handle tool authentication requests. Set the before_action callback below to run on that action, and pass the data needed.

  • redirect_url (required): the authorization redirect URL to continue the login flow
  • redirect_data (required): all form data required for the authorization redirect, which the previous login render call should have included in a form tag.
  • window_type: (optional) Set to :new_window to open the tool in a new tab or window, or to :popup to open in a popup window. Defaults to :new_window.
  • width: (optional) The width the popup window should be, in px. User has the discretion to ignore this. Only valid with window_type: popup. Defaults to 800px.
  • height: (optional) The height the popup window should be, in px. User has the discretion to ignore this. Only valid with window_type: popup. Defaults to 600px.


include CanvasLtiThirdPartyCookies::RelaunchOnLogin
  state, nonce = create_and_cache_state # handled elsewhere
  redirect_url = ''
  # this data is part of the OIDC Launch Flow as defined here:
  redirect_data = {
    scope: 'openid',
    response_type: 'id_token',
    response_mode: 'form_post',
    prompt: 'none',
    redirect_uri: tool_launch_url, # defined elsewhere
    client_id: params.require(:client_id),
    login_hint: params.require(:login_hint),
    lti_message_hint: params.require(:lti_message_hint),
    state: state,
    nonce: nonce

  (redirect_url, redirect_data)

Local Development

To make changes and test them locally, it's possible to install this gem in a local LTI tool installation.

  1. In your tool's Gemfile, add , path: './cookies' to the line for this gem.
  2. In your tool's docker-compose.override.yml, add a new volume to link this gem's folder to the ./cookies path. It should look like this: volumes: - ./path/to/lti_third_party_cookies:/usr/src/app/cookies
  3. bundle install again.

The same process is possible with non-docker-compose apps, just directly point to the gem's folder from your tool's Gemfile.


Ruby Tests

The ruby tests use minitest, which comes bundled with rails. To run them locally:

rails test

There is a Docker setup for running tests that's used for CI. To run them in Docker locally:

docker build -f test/Dockerfile.ruby -t ruby-test .
docker run --rm ruby-test rails test

Javascript Tests

The JS tests are self-contained in the test/javascripts folder. To run them locally:

cd test/javascripts
yarn install
yarn test

There is a Docker setup for running tests that's used for CI. To run them in Docker locally:

docker build -f test/Dockerfile.node -t node-test .
docker run --rm node-test yarn test


All user-facing strings in this gem are localized and translated into the languages that Canvas supports. Any time that any strings are changed, you should make sure this is run:

rake app:i18nliner:dump

Then, add config/locales/generated/en.yml to your commit.

After making changes to en.yml, you will need to push these changes to the instructure-translations S3 bucket so that the translators can update other locales. TODO: the script or Jenkins job to actually push to S3.

Once translators have finished updating other locales, they will notify you, and you can pull down the new locale files into config/locales, and commit and push them. TODO: the script or Jenkins job to pull from S3.

Publishing New Versions

  1. Bump the version in lib/canvas_lti_third_party_cookies/version.rb.
  2. rake install
  3. Commit, push, and merge that change.
  4. gem push pkg/canvas_lti_third_party_cookies-<version>.gem
    • note that this will only work if you have access