bluesky / tiled

API to structured data
https://blueskyproject.io/tiled
BSD 3-Clause "New" or "Revised" License
59 stars 50 forks source link

OAuth2/OIDC Configurable Redirect for Browser Apps #141

Open dylanmcreynolds opened 2 years ago

dylanmcreynolds commented 2 years ago

Trying to write a login page from HTML that works with Tiled using ORCID as the authenticator. (Everything herein is about ORCID, but should apply to any OIDC IdP.)

The general flow I'm trying to setup is:

  1. login.html sends the user to ORCID saying "I want: return type of code, oidc scope and redirect back to me when the user is done"
  2. the redirect comes back and the login.html says "oh, there's a code in the query params of me"
  3. It then sends the code to tiled at /auth/code
  4. tiled then does a behind-the-scenes post back to ORCID with the code, client id and client secret
  5. tiled will then respond with it's own access_token and refresh_token

This works as long as I configure the tiled to use the one and only login page.

But what if a single tiled server supports multiple web pages from multiple realms?

That isn't possible now, because when tiled exchanges the code with ORCID (https://github.com/bluesky/tiled/blob/cfa0e45b5042049bb2fccc2f673160bf1e0d058b/tiled/authenticators.py#L126) the code that ORCID is checking was built with the redirect_url of the web page. But tiled is building the message using the "configured" redirect_url that works for our python clients (and notebooks). ORCID rejects this message because that was not the redirect_url that the code was created for.

I propose enhancing tiled to configure multiple redirect_urls (like OIDC IdPs do)? Then we could send the redirect_url we want in a query param of the /auth/code. If the provided redirect_url wasn't pre-configured, then we reject the message. But if it was, then we send it to ORCID so that it can correctly validate the code.

Also, we need to look into supporting PKCE. https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-11#section-2.1.1 I'm not sure what all will go into that but putting it here for discussion.

dylanmcreynolds commented 2 years ago

About PKCE, this is a nice discussion: https://dropbox.tech/developers/pkce--what-and-why-

dylanmcreynolds commented 2 years ago

To add more information about PKCE. Until recently, SPA (and mobile apps) were guided towards implicit flow for a number of reasons. Without cross-site scripting capability, following code authorization flow would leak the client ID in the browser. So, implicit flow was as good as it got.

Then, CORS came around, allowing the browser to potentially post to the IdP (ORCID) and Service (Tiled). This opens the door for the Service (Tiled) to perform the code exchange behind the scenes. Because that's now possible and it's more secure, the recommendation is to do it. However...because this is still through the browser, the code (coming from the IdP (ORCID) to the Service (Tiled) is now viewable. So, the recommendation is to add PKCE to the mix so that the IdP (ORCID) can verify that the request came from the same browser that asked for the code. Here's how I think this will work:

  1. Browser redirects user to ORCID, adding OAuth bits to the url. https://orcid.org/oauth/authorize? response_type=code &redirect_uri= &client_id= &scope=openid &code_challenge_method=S256& code_challenge=c2x9RGpVeF9GdIJ6AgIHnYzyl7gXPatBNtUT43BCLrQ

  2. Once the user logs in, ORCID redirects the browser back to the adding ?code=<code> to the url

  3. Browser app posts the code and the code_verifier that was used to create the code_challenge to Tiled (at /auth/code)

  4. Tiled then adds the code_verifier and code_challenge_method to the message it already sends to ORCID.

  5. ORCID validates the the code_challenge and code_verifier match before issuing an access_token.

Now, this seems pretty important. Unfortunately, it looks like ORCID does not yet support it! (https://trello.com/c/3A5mdVZ1/289-oauth-pkce-cors-support) But I surely wanted to get this typed out for posterity.