At the end of .NET LTI Project - Part 2 - Launch Request we were authenticating and grabbing an access token for the logged in user. But, each time the application was launched it was creating a new token, filling the user profile with multiple Access Tokens that would need to be manually deleted.
This is messy, and for a user who keeps an eye on their profile will likely look suspicious. So there are two possible solutions to prevent the build up of excess useless tokens:
Take advantage of the "refresh token", and only ever create a single access token
Delete the token each time a user session expires, or when the user exits your application
The good news is, the heavy lifting is done. Only some logic to be added to leverage the existing methods that we have already worked through.
To follow along with this post, download the associated source code from this git branch:
If you users will be spending more than one hour at a time in your application, or if they will be leaving and coming back at a later date, refreshing the token you already have is something you should consider. At the very top of the Canvas OAuth2 document, in a bright orange box, it clearly states that a token has a 1 hour life span, and refresh tokens must be used:
For the sake of this simple demonstration project, I've added a field to the [accessTokenCache] table.
In the SqlServer folder, execute the accessTokenCache.sql script to recrete the table with the new field.
This is not the ideal place to store this value, in a live environment this would be an actual config varible.
However, this will allow the code in this part of the project to grab this value for you, for the sake of the exercise.
Detecting the Existing Access Token
The logic changes for this demo application to handle the token refresh is restricted almost exclusively to the HomeController.oauth2Request() method. Storage and retrieval of the access tokens was included in
Of course we are still validating the OAuth signature, to ensure the LTI launch request is coming from a valid source.
Now, as soon as we validate the signature, we try to retrieve a previously stored token for the user.
If we found a toke, we check the life of that token to see if it needs to be refreshed. To handle this I added a simple property to userAccessToken: tokenRefreshRequired
If we have found an existing token, and it requires a refresh, we call our existing method: requestUserToken
This time when we make the call we pass a different value for grantType: refresh_token
The existing logic will take over, and Canvas will return to you a new token.
The user token can expire at any time during the use of your application, not just during the initial LTI launch. Once the user has launched your application, your application will likely continue to make API calls to Canvas for specific information. If the user stays active in your application for one hour or more, the token will expire on the Canvas server and you will need to refresh. Each time you prepare to make an API call you need to check to see if the token has expired, and if it has you need to execute the token refresh logic.
Scenarios to get you thinking about testing:
Delete the access token in the Canvas user profile. How do you want to handle that?
Manually set the token timestamp in the database to force a token refresh. Where in your application do you need to consider token expiration?
Delete the access token from the database. What affect does this have on the Canvas user profile?
You can test this scenario with this simple demo application. As your business logic becomes more complex you will need to consider handling this potential scenario in all appropriate places.
If you want to delete user access tokens as you go, then you want your application to execute the OAuth2 Logout.
This is how you clean up the list of your application tokens in the user profile settings.
The sample application for this blog includes a simple "Logout" button and associated method to execute the logout API call. This example also shows how to recover the LTI parameters from the database, where we stored them in the [stateCache] table.
For purposes of a simple demonstration, the unique state id is stored on the client page in a hidden field named "stateToken". The value of the "stateToken" is passed back to the client during validation, the submitted back to the server when the logout button is clicked. Passing the state id in this fashion allows you to continue to track the client even when they have cookies disabled.
Hidden fields are not necessarily the best way to do this, but is very easy for the purpose of this simple demo app.
Do your own research on state-less applications and decide on what method is best for you.
This three part blog should give you an overview of OAuth2, and hopefully help some get over the hump.
If anyone has ideas to add, please comment and start a discussion.
Keep in mind that this application is meant only as a deomonstration of how to execute the OAuth2 workflow. As you develop your own applications, make sure you flush out your test scenarios and understand what the expected behavior is.