Skip to content

Instantly share code, notes, and snippets.

@gigamonkey
Created June 25, 2024 19:13
Show Gist options
  • Save gigamonkey/d8dd84f24da984202c071e0536c0e332 to your computer and use it in GitHub Desktop.
Save gigamonkey/d8dd84f24da984202c071e0536c0e332 to your computer and use it in GitHub Desktop.
Trying to wrap my brain around OAuth (again)

Trying to understand OAuth

Here’s my basic understanding of how to do client-side OAuth, meaning we want JavaScript running in the browser to be able to authenticate to a service (e.g., GitHub) and then access that service directly from the browser. The key security requirement is that the client-side JavaScript can’t know the client secret because the whole point of secrets as that they be secret. Assume in what follows that our web server is running on example.com. Also note that all these requests should be over HTTPS.

User/client initiates login

The browser makes a request to our web server to initiate the login process at a URL like:

https://example.com/login

For example, the user clicks a "Login" button which triggers a request to the endpoint. Or possibly our client-side Javascript notices whenever we are on a page that requires authentication and we don't have a Github access token in our session storage and kicks things off by setting window.location.href to the login URL.

In either case, before making the request (i.e. in the onclick of the button or in the auto-login code), we store the current value of window.location.href in sessionStorage as preLoginURL or something.

Generate state and redirect the browser

The server generates a secure random STATE parameter (i.e. a cryptographically secure random string) to prevent Cross Site Request Forgery (CSRF) attacks and stores it in the user's server-side session.

The server then constructs the GitHub authorization URL with the state parameter included and sends a redirect response to send the browser there:

https://github.com/login/oauth/authorize?client_id=CLIENT_ID &redirect_uri=REDIRECT_URI &scope=repo,user &state=STATE

  • CLIENT_ID is the ID created by GitHub when we set up our OAuth app which the server knows from it's configuration (e.g. dotenv or whatever)

  • REDIRECT_URI is the URL on our web server that GitHub should redirect the browser to after the user has authenticated. Also from configuration. It has probably been configured as part of the OAuth app and Github may reject this request if it doesn't match.

  • scope parameter tells GitHub what scopes we are asking for so it can inform the user.

  • STATE is the secure random value produced by our web server.

User authenticates

The user sees a page served by github.com that asks them to log in and authorize the app to have the requested access. If the user approves, GitHub redirects the browser to the REDIRECT_URI with a code and state parameter:

https://example.com/callback?code=SOMECODE&state=STATE

  • SOMECODE is an opaque code generated by the GitHub server.

  • STATE is the same value that was passed in the authorization url.

The purpose of this step is to communicate these two pieces of information back to our web server, associated with this user. When the browser is redirected it sends the two query params but also it's cookies for our web server which allows our web server to identify it's session.

Server verifies the state parameter

Our web server receives the callback request, and verifies that the state parameter matches the one in the user’s session. If it doesn’t match returns an error response.

Exchange code for access token

The goal at this point is to trade in the code generated by Github for an access token and to pass it back to the the client-side Javascript so it can use it to access the Github API.

The key thing that happens at this step is our web server unites the code given to us by Github (which Github will have kept track of and associated with the CLIENT_ID) with the CLIENT_ID and the CLIENT_SECRET which allows Github to know that this is a legit request from code owned by the owner of the OAuth app. So our web-server posts a request to:

https://github.com/login/oauth/access_token

passing the CLIENT_ID, CLIENT_SECRET, code, and for good measure the REDIRECT_URL so Github can double check that it matches. Presumably Github does something roughly like, looks up the CLIENT_ID it stored associated with the code when it generated the code and then checks that the CLIENT_SECRET and REDIRECT_URL are the correct values for that CLIENT_ID. Assuming so it responds with an access token.

Pass the access token to the client-side Javascript

Now our web server needs to send some response to the browser that allows it to communicate the access token. The simplest and most secure way is to set a Secure, HttpOnly cookie containing the token on the successful response to the /callback endpoint.

Getting back to where we started

Finally we want our user to end up back on the page they were on when they started logging in. If we stored the starting page in sessionStorage under preLoginURL as suggested in “User/client initiates login” we can get back there if the response to /callback contains Javascript that sets window.location.href to that stored URL and the user will be back where they started, now with a Github access token stored in sessionStorage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment