How to Validate LTI Requests with a Custom Warden Strategy

Authorization with LTI

Natalie Perpepaj
Apr 24 · 6 min read

In a Rails application, have you ever found yourself wanting various ways to authenticate incoming requests based on some kind of conditional?

Well, neither did I until the introduction of LTI (Learning Tools Interoperability). We’ll dive into what that is in a second, but first, let’s think about some use cases for supporting multiple authorization strategies. Maybe you want to handle the authorization of some API endpoints differently from the rest, or you want the authorization process to change depending on the specific params provided, or you might even want to change the authorization flow depending on the environment the app is in.

I found myself in the latter scenario recently when we were given a new requirement that all incoming requests in a specific environment only had to follow the LTI standard. All other environments would continue using our existing credentials + password auth flow, so we couldn’t replace it altogether. Instead, we needed to support two different auth flows side-by-side… *Warden Strategies enter stage right*

A brief overview of Warden Strategies

For us, the discovery of Warden Strategy was invaluable. So what is Warden? Warden is a gem that can be used in Rack-based Ruby applications to introduce authentication systems in the app’s middleware. Warden provides session handling and uses a rack object to set auth information (including the user object).

A Warden Strategy houses the logic for request authentication. Multiple “cascading strategies” can be used in a single application. Warden will run through all existing strategies until one succeeds, fails, or if none are found applicable for the particular request. A warden strategy often consists of at least two methods:

  • #valid? (Optional method) — provides a guard against inapplicable strategies Evaluates to true or false; if true, the strategy will run. If false, strategy will not run. Without this method, the strategy will run by default
  • #authenticate! (Required method) — executes the authentication logic Can either halt the strategy stack or pass onto the next strategy (passing occurs by default if the halt! action is not called)

All strategies have request-related methods and action methods inherited from Warden::Strategies::Base that can be used within the strategy. These methods can trigger the halting of the strategy stack or ‘login’ a user if authentication is successful.

Example strategy below:

Our custom LTI Warden Strategy

Now that we have a little background on Warden Strategies, let’s dive into the custom strategy we created to validate the LTI requests. To start, LTI is a protocol that allows “learning tools” (applications) to securely communicate with each other. An LTI request is just a POST request with pre-defined params, including an OAuth signature, shared key, and other OAuth/request information. For our specific case, only valid LTI requests should be filtered through. Therefore, the authentication strategy for these requests should be the first and only strategy run, so we add the strategy, and then ensure it is the first in the stack:

NOTE: we opted for housing the lti_auth strategy logic in a separate class — LtiAuth::WardenStrategy — as opposed to a do block as seen above)

Now that we’ve added the strategy, we want to ensure that it is only ever run given the appropriate environment:

We do this using the #valid? method. In this particular case, if LtiAuth.on? is true, the strategy will run; otherwise, it will not.

If we are in an environment in which this strategy should be used, the next method called is #authenticate!. This is where the authentication logic gets executed. As mentioned before, LTI request parameters include an OAuth signature. Because Warden provides access to request methods, we are able to pass the request information to the LtiAuth::RackOauthVerifier, which validates the authenticity of the signature (an encoded combination of the request method, url, params, and a shared key and secret). We also want to ensure that required user credentials are provided. If both the LTI signature is valid and the correct user params exist, we go ahead and provision the user.

Okay cool, that’s great and all, but how do we stop any following strategies from executing and how do we get logged in? Great question. As mentioned above, Warden provides action strategy methods. The #halt! method does exactly that: it halts the cascading of strategies. As used above, both #success!and #custom! trigger a #halt!. If successfully validated, the #success! method receives the authenticated user object and sets the session and user information in the warden rack object. If authentication fails, the #custom!method receives a custom rack array and sends this array as the response to the request without moving forward into the application. For more information on these actions, including #fail!, #redirect!, and #pass, checkout again the Warden Wiki.

And that’s it! We’ve authenticated our request and the user is logged in. So we’re done, right? Well, not quite.

With a session set, Warden will skip all strategies upon subsequent requests. However, what if I want to verify if the session user is the appropriate user and that the OAuth signature provided is still valid? *Enter #after_set_userstage left*

Warden provides us with a warden_manager object. This object provides an #after_set_user method that gets called every time after a user is set upon request or when a user is set based on preexisting session data upon subsequent requests. We created a LtiAuth::WardenAfterSetUSer class to help us validate the user and the provided signature.

If both are correct, we go ahead and proceed as usual. If either the user is invalid or the signature is invalid, logout the user. Logging out a user will trigger the cascading strategy stack to be run again. So our lovely authentication strategy has another stab at making things right.

Work better with diagrams? Same. Here are the same authentication flows discussed above in a nicely illustrated visual format, to help round things out.

Thanks for reading! Want to work on a mission-driven team that loves LTI and flexible authorization strategies? We’re hiring!

To learn more about Flatiron School, visit the website, follow us on Facebook and Twitter, and visit us at upcoming events near you.

Flatiron School is a proud member of the WeWork family. Check out our sister technology blogs WeWork Technology and Making Meetup.

Flatiron Labs

We're the technology team at The Flatiron School (a WeWork company). Together, we're building a global campus for lifelong learners focused on positive impact.

Natalie Perpepaj

Written by

Flatiron Labs

We're the technology team at The Flatiron School (a WeWork company). Together, we're building a global campus for lifelong learners focused on positive impact.