LTI OIDC Login with LTI Client Side postMessages specification
LTI OIDC Login with LTI Client Side postMessages specification
I've been experimenting with this specification and have a few questions/comments:
- Is there a document available anywhere which outlines the other options which were considered and reasons why they were not selected? For example, very short-lived (e.g. < 10 seconds) state and nonce values is one solution I have been using. Is ensuring that the response to the authentication request comes from the same browser session (as the use of postMessages in this spec does) of high importance even within such a short timespan? Would another possible solution also be to have the tool use browser storage? Both of these could be implemented by the tool without depending upon the platform, so I would be very interested to read anything which is available about the decision process behind this spec.
- The spec appears to require the platform to implement the storage within the parent or opener window (or a frame within this window). I know of one early adopter of the spec which is using the top window (which is not always the parent) and, whilst I expect they will be changing their implementation to conform to the spec, I wondered whether this restriction was really necessary and might impact other platform implementations. Perhaps just adding top as another option might be useful. Has this been considered and rejected?
- If the lti_storage_target parameter is passed and gives a different frame name from the capabilities response, should the one which a tool uses be prescribed, or just left for a tool to decide? The spec implies that the capabilities response takes precedence, but it also suggests that the lti_storage_target parameter should be ignored when no frame element is specified. I think the order of precedence should be frame given in the capabilities response first, frame given in the lti_storage_target parameter second, or assume the window object should be used when neither is available.
- I understand the need to ensure that keys used for saving data are unique, but rather than using a name like "my_tool_nonce_[nonce_value]" has it been considered to use a name of "my_tool_nonce_[state_value]" instead (similar to state values being saved with a key of "my_tool_state_[state_value]")? This would have the benefit of not only ensuring that the nonce value is valid, but also that it was issued with the current state value.
- I think there is a use case where this proposed solution will not work, though it may not be an important one. If a platform is opening a tool in a new window but does so by first opening its own URL in this window which then redirects/auto-submits to the tool, then neither the window.parent nor the window.opener objects will refer to the platform window where the storage is likely to reside. In this case I suspect a tool will have to rely on cookies (or just the short-lived state and nonce values passed back in the authentication response). My initial tests suggest that this use case applies to at least one certified platform where, in the tool window, both window.parent and window.opener are the same as window.self.
- A minor point, but I think lti_set_data would be a better name than lti_put_data for the postMessage subjects; this would be consistent with general references to getter and setter methods, and the use of getItem and setItem methods in the window.localStorage API.
- I believe that window.parent will always return an object (even if it is just window.self) so I think the code "let target_window = window.parent || window.opener;" should have the condition reversed to be "let target_window = window.opener || window.parent;".
Thanks for any insights anyone is able to provide on the above.
Some responses
Howdy Stephen,
We discussed these in the call today and have some notes for you:
1) This would be a great section in the implementation guide indeed. Things like local storage get blocked in many of the same use cases as cookies for example, and explaining that clearly will help implementors understand.
2) I didn't understand Martin's explanation.
3) We should maybe specify that they names should be consistent, but if not do a specific order of preference.
4) The naming is totally up to the tool, so if fetching the nonce with the state key value is useful, that's fine for them.
5) Yes, this is probably a good one to point out so that folks understand it could happen. This should be taken care of because cookies would be allowed in this case and implementations should favor cookies when available. We could also be handled if you set the right values when doing window.open.
6) Yeah, may have been good, we were using map put/get vocab, but we're sticking with the current ones given the implementations out there.
7) Thanks.
Re: Some responses
Thanks for the responses.
Re 1), I have yet to experience my use of localStorage being blocked (except by the user in their browser settings, or by a quota being reached) so more information about the challenges with using this solution would be very welcome.
I assume from your answer to 2) that there are no plans to allow the use of window.top.
With respect to 3), I am wondering whether the frame name needs to be given in the query parameter; this would thereby avoid any conflicts. Since a tool will need to check the capabilities anyway to see whether a different frame is specified for a particular message type, all it really needs in the initiate login request is an indication that the platform supports storage postMessages; e.g. lti_storage_target=true, or just lti_storage=true. I am also curious as to whether there is a reason why this parameter is being passed in the query string, rather than being defined as an additional login parameter (like the lti_message_hint and lti_deployment_id parameters in section 4.1 of the LTI 1.3 core spec). Doing so would allow tools to locate all the parameters in the same place (POST data or query string) depending upon how the request was sent.
Thanks.
Re: Some responses
Re 1), after some further exploration I now see that some browsers also prevent access to localStorage when third party cookies are blocked so I understand why this is not a suitable option. But since a state and nonce value are being passed back to the tool in the response to the authentication request, the only benefit I can see from this proposed use of postMessages is that it ties the values to the same browser session. Is this deemed to be important, or am I missing some other benefits of this specification?
Apart from Blackboard Learn which seems to support what I assume is an earlier version of this spec, are there any other platforms which support it at this time?
Thanks.
Implementations
I have now added support for this draft spec to the ceLTIc Project's open source PHP LTI class library so, unless it is disabled, the platform storage will be used automatically by tools when third-party cookies are being blocked. The LTI Platform plugin for WordPress has also been updated with an option to offer tools storage by the platform. I simplified the implementation proposed in the spec by using only a single
lti.put_data
postMessage with a name of thestate
and a value of thenonce
. That saves having to wait for two responses (for put and get messages); the presence of a value confirms the validity of the state, and the value itself can be checked to confirm the validity of the nonce. This also has the added benefit of confirming that the state and nonce were generated for the same message. I remain interested to hear about any other implementations which exist.One omission from the spec is a
delete_data
postMessage which could be used to remove saved values after they have been processed and ensure they cannot be re-used. Has this been considered and rejected? Of course, it could also be achieved by using put_data messages with empty values.However, I still think I am missing something about this spec. It does not solve the third-party cookie issue (for which, I suspect, the only solutions are to avoid the use of cookies or force the tool to open in a new window). All it seems to offer is a place where a tool can store data, which is something it can already do on its own server, so it appears to offer nothing to a tool which it cannot already do. Since the state and nonce values are passed in the message body of the request and response, a tool will always get the values returned which it can then verify as being ones it generated, and doing so does not require a cookie. Using a cookie would ensure that both state and nonce values relate to the same message, but this relationship could be stored by a tool anyway so that the nonce can be looked up based on the state value received. The one extra benefit offered by this spec is that it ensures that the response is sent from the same browser session that sent the authentication message request. But the spec does not seem to value this benefit since it suggests that the storage should only be used when cookies are not supported. What am I missing here? Is this spec adding anything which cannot already be done? Or is it just offering an alternative method?
Thanks.
Thanks for the combined state
Thanks for the combined state/nonce suggestion, I agree that's nice and might be a better way for the implementation docs to suggest.
Also, yeah, a delete operation would be good, I don't remember how much that was discussed, we may beover-relying on the intention that the data is all temporary to that page load session.
When LTI adopted the OIDC flow in order to be more standardized, and also importantly solve the security problem that the same browser that starts the LTI launch flow needs to be the one finishing it, it also introduced a dependency on cookies to even do an LTI launch at all. So regardless of whether a tool required cookies on its own for sessions, LTI added a new dependency. This specification creates the work around for that dependency that was added.
There isn't a generic solution to applications that require cookies for session and want to live in an iFrame without busting out. There are many strategies for dealing with those situations and the browser community seems to be moving forward on the sandboxing approaches that I think will be a huge benefit to the LTI community. So hopefully that will continue to evolve and make developer lives easier for these issues.
Re: Thanks for the combined state
Thanks for the reply, Bracken. However, I remain unclear about the statement that an LTI 1.3 launch has a dependency on cookies. All the draft spec appears to do is provide a place where a tool can store data (state and nonce in this case). But tools could equally use their own storage solution to store this data; the absence of cookies does not prevent tools from being able to store data, though when cookies are available this is the typical solution. For example, a tool could use a memory cache for saving the state and nonce values, or just use a database table. Since I would expect a tool to be checking that the target_link_uri value has not changed between the initiate login request and the response to the authentication request (the LTI 1.3 spec requires these to be the same), it is going to have to store this value too. It could use the storage provided by the platform for this (though this is not mentioned in the spec), but it could equally store it locally. If a tool did wish to use a session when cookies are being blocked it could also just include the session ID within the value passed as the state, thereby also avoiding the need for this storage. So am I missing something still, or is this spec just providing an alternative way for tools to store data when handling an incoming LTI message, and it is not actually necessary for LTI 1.3 to function when cookies are blocked?
Do you (or anyone else) have any comments on the suggestions I made in my earlier message to simplify this spec? In particular, avoiding offering two places in which to specify the name of the storage frame and treating the
lti_storage_frame
parameter in the same way as the other additional login parameters (like thelti_message_hint
andlti_deployment_id
) so they are all found in the same place.Thanks.
It is required to validate
It is required to validate that the same browser that started the LTI launch is the one finishing it, and the way OIDC validates that is by having the state saved client side then you compare that to what you have server side during the OIDC Auth Response. A tool that only validates state via server-side data is not fulfilling the steps necessary for that OIDC requirement.
So, if a tool does not have cookies available to transmit that client side state, they need another client side means to do that. That's what this spec is meant to provide.
I missed that lti_storage
I missed that
lti_storage_frame
comment, I'll bring that up to the group, though it may be too late in the process to update that.Re: It is required to validate
Thanks for the quick reply. Since the cookie is just passed as a header in the request message, it would also be possible for this to be faked so surely it cannot be 100% reliable. The use of the storage in the platform's browser frame seems to me to be the only method of being 100% sure that the response came from the same browser session. In which case why not make the use of this spec required for all LTI messages, whether cookies are available, or not?