You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
LMS uses OAuth 2.0's "authorization code flow" (also sometimes called "three-legged OAuth") to get access tokens that it uses to make requests to various third-party APIs (the Canvas API, the Blackboard API, etc).
Testing LMS's authorization code flow
For example to test the authorization code flow with Canvas:
Start the LMS app locally (make dev). You'll also need h, the client, Via and Via HTML running (as LMS depends on all of these)
Also make sure you've run make devdata for each app
Make sure you don't have any access tokens already saved in your LMS DB, as this would remove the need to go through the OAuth flow:
You should see an Authorize Hypothesis dialog. When you click the Authorize button it should open a Canvas page in a popup window asking you to authorize Hypothesis to access your Canvas account. When you click this second Authorize button the popup window should close and the assignment should launch successfully--LMS has received an access token from Canvas and has used it to make the API requests necessary to launch the assignment. If you look in the DB again you'll see the access token.
h needs to implement OpenID Connect's authorization code flow, not OAuth 2.0's. (They're very similar.)
h does not need to use a popup window. LMS needs to use a popup window for security reasons because it's running in a third-party iframe inside Canvas. h can just redirect the top-level tab to ORCID's authorization URL.
I don't think h will need to store access tokens in the DB, or use access tokens at all. h doesn't need to make API requests on behalf of the ORCID user, it just needs to identify who the user is.
h needs to receive and parse an ID token to find out the user's unique ORCID iD. This is an OpenID Connect thing not an OAuth 2.0 thing. In the last step of the flow, when LMS receives an access token, h will be receiving an ID token instead. I don't think we'll need to store the ID token--just parse and verify it.
I think h is going to need to retrieve public keys from ORCID to decrypt and verify the encrypted and signed ID tokens, and is going to need to handle when ORCID rotates their keys.
How LMS's flow is implemented
Let's walk through how LMS implements its OAuth 2.0 flow. h doesn't necessarily need to implement its own flow in exactly the same way. But this will probably be an instructive example anyway. The code for the Canvas API got a bit messed up when someone "refactored" it at some point, some I'm going to walk through LMS's Blackboard API integration instead:
When the user clicks the first Authorize button the app opens the URL https://lms.hypothes.is/api/blackboard/oauth/authorize in a popup window. This URL is handled by the authorize() view. This view just redirects the popup window to Blackboard's authorization endpoint (https://MY_BLACKBOARD_INSTANCE.com/learn/api/public/v1/oauth2/authorizationcodet) with various query params on the URL including: our Blackboard client ID, a randomly generated state (for CSRF protection), and our redirect URI (which we've registered with Blackboard).
The popup window now shows Blackboard's https://MY_BLACKBOARD_INSTANCE.com/learn/api/public/v1/oauth2/authorizationcode page and when the user clicks the second Authorize button on this page Blackboard redirects the popup window back to the redirect URI which we previously included in a query param (see above).
Our redirect URI is https://lms.hypothes.is/api/blackboard/oauth/callback and is handled by the oauth_redirect() view. Blackboard includes an authorization token in a query param, which we exchange for an access token.
Note that this view is protected by OAuthCallbackSchema: the view won't be called unless the request passes this schema's validation. The schema makes sure that the request has a code param that we'll need in the next step, and also that it contains a valid state param (which we sent to Blackboard in a previous step, and Blackboard now sends back to us) for CSRF protection.
Make an access token request and save the token in the DB.
Send an OAuth 2.0 "access token request"
(https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3) to get a
new access token for the current user and save it to the DB.
:raise ExternalRequestError: if the HTTP request fails
:raise ValidationError: if the server's access token response is invalid
"""
self._token_request(
token_url=token_url,
auth=auth,
data={
"redirect_uri": redirect_uri,
"grant_type": "authorization_code",
"code": authorization_code,
},
)
LMS now automatically closes the popup window and continues on with launching the assignment, using the access token that it now has in its DB to make API requests. In h's case this will mean (for example) logging the user in and redirecting them to their profile page.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
LMS uses OAuth 2.0's "authorization code flow" (also sometimes called "three-legged OAuth") to get access tokens that it uses to make requests to various third-party APIs (the Canvas API, the Blackboard API, etc).
Testing LMS's authorization code flow
For example to test the authorization code flow with Canvas:
Start the LMS app locally (
make dev). You'll also need h, the client, Via and Via HTML running (as LMS depends on all of these)make devdatafor each appMake sure you don't have any access tokens already saved in your LMS DB, as this would remove the need to go through the OAuth flow:
Log in to https://hypothesis.instructure.com/login/canvas as
eng+canvasteacher@hypothes.is(password in 1Password)Launch an assignment that requires access to the Canvas API, for example localhost (make devdata) Canvas Files Assignment
You should see an Authorize Hypothesis dialog. When you click the Authorize button it should open a Canvas page in a popup window asking you to authorize Hypothesis to access your Canvas account. When you click this second Authorize button the popup window should close and the assignment should launch successfully--LMS has received an access token from Canvas and has used it to make the API requests necessary to launch the assignment. If you look in the DB again you'll see the access token.
Differences between what h needs to do
What h needs to do for Log in with ORCID is slightly different:
How LMS's flow is implemented
Let's walk through how LMS implements its OAuth 2.0 flow. h doesn't necessarily need to implement its own flow in exactly the same way. But this will probably be an instructive example anyway. The code for the Canvas API got a bit messed up when someone "refactored" it at some point, some I'm going to walk through LMS's Blackboard API integration instead:
When the user clicks the first Authorize button the app opens the URL
https://lms.hypothes.is/api/blackboard/oauth/authorizein a popup window. This URL is handled by theauthorize()view. This view just redirects the popup window to Blackboard's authorization endpoint (https://MY_BLACKBOARD_INSTANCE.com/learn/api/public/v1/oauth2/authorizationcodet) with various query params on the URL including: our Blackboard client ID, a randomly generated state (for CSRF protection), and our redirect URI (which we've registered with Blackboard).lms/lms/views/api/blackboard/authorize.py
Lines 10 to 41 in 1ffa2cf
The popup window now shows Blackboard's
https://MY_BLACKBOARD_INSTANCE.com/learn/api/public/v1/oauth2/authorizationcodepage and when the user clicks the second Authorize button on this page Blackboard redirects the popup window back to the redirect URI which we previously included in a query param (see above).Our redirect URI is
https://lms.hypothes.is/api/blackboard/oauth/callbackand is handled by theoauth_redirect()view. Blackboard includes an authorization token in a query param, which we exchange for an access token.Note that this view is protected by
OAuthCallbackSchema: the view won't be called unless the request passes this schema's validation. The schema makes sure that the request has acodeparam that we'll need in the next step, and also that it contains a validstateparam (which we sent to Blackboard in a previous step, and Blackboard now sends back to us) for CSRF protection.Once past the validation schema the view calls
BlackboardAPIClient.get_token(authorization_code_from_query_param)which delegates toBasicClient.get_token(authorization_code)which delegates toOAuthHTTPService.get_access_token(). This method makes a server-to-server request to Blackboard's token endpoint to exchange the authorization code (and various other parameters, including an authorization made up of our Blackboard API client ID and client secret) for an access token. It then callsOAuth2TokenServiceto save the access token to the DB.lms/lms/services/oauth_http.py
Lines 78 to 97 in 1ffa2cf
LMS now automatically closes the popup window and continues on with launching the assignment, using the access token that it now has in its DB to make API requests. In h's case this will mean (for example) logging the user in and redirecting them to their profile page.
Beta Was this translation helpful? Give feedback.
All reactions