Table of contents
- Introduction
- Idea Behind OAuth
- Enough talking Let's Code!
- Generalizing GitHub-specific Patterns for other OAuth Providers
Introduction
So... what is this... "OAuth" huh?
This is what they say:
OAuth is an open-standard authorization protocol or framework that provides applications the ability for secure designated access.
Gibberish, let me break this down in layman's terms
This is hashnode's login page βπ»
The buttons you see here - "Continue with Google / GitHub / LinkedIn / Facebook / Apple" use The OAuth Flow to redirect you to a third-party service that will identify you for hashnode (while you allow yourself to be identified by this service & give read access of your profile to hashnode) and this service will then redirect you back to hashnode's website saying "here are the credentials of this person who wants to signup on your website, we've verified him, you're good to go"
In this post, we'll implement this OAuth flow using GitHub & NextJS.
Let's do this!
Idea Behind OAuth
Let's say you're making a tweet scheduler app,
They come on your app, write a tweet & schedule it,
It's your job now to save that tweet & later post it on Twitter on their behalf.
You're in a dilemma now:
On the one hand, you can't ask the user for their account name & password just to make a tweet because they wouldn't trust your app.
On the other hand, if you go to Twitter saying "hey Twitter, a user with the name
@johndoe
wants to post this..."
Twitter simply wouldn't allow that because anyone can tweet on anyone else's behalf then!
This is where the Standard OAuth Protocol comes in.
You redirect the user to Twitter's website and indicate what you want access for, user now has the power to authorize your app or disallow it from having tweet post access to your account,
Twitter will then accordingly redirect the user back to your website giving you either code
or an error.
The code
is a string of utf-8 characters that Twitter generates and it is used to Identify:
Your app
The user
The permissions user has agreed to authorize your app.
Note: This flow is kept a standard for all services that can help identify people & authorize other apps on users' behalf to read/write data.
Enough talking Let's Code!
yarn create next-app
This command will create a starter NextJS app,
Name your app, select JavaScript or TypeScript (doesn't really matter here, I went with TypeScript),
And you're all set up!
Why did I choose NextJS?
Well you see, implementing OAuth requires a server-side node API endpoint and with this framework, we can have all our server-side (Node) & client-side (React) logic in one place.
You're welcome to use any other backend framework here as long as it follows the same logic we'll be using in this post.
There remains one more question: "Why do I need a server-side endpoint anyway?" to which I'd suggest you to read along as it unfolds.
(hint: for secure communication between server & oauth service)
Now, I need you to head over to your GitHub account > Settings > Developer Settings > OAuth Apps and Create a new oauth app
The most important field here is the "Authorization Callback URL" which we set to our server-side API endpoint - http://localhost:3000/api/auth
We are to make a file auth.ts
under the api
folder yet.
This is the URL GitHub will redirect the user to, after the user authorizes or rejects your authorization request.
Go ahead, register your application & then generate the client secret
GitHub needs the client id & client secret to identify your app and redirect the user to the appropriate callback URLs
Now, let us head over to the NextJS app & create a .env
to store client id & secret in one place.
Why store secrets in a .env file instead of let's say a "config.js" file?
The first & foremost reason is that you can add .env
to a .gitignore
and it will be skipped by git when hosting the code on GitHub.
Now, when you deploy the code to some service like Vercel, they ask for "Environment Variables" and you can just add the client id & secret over there.
This serves 2 important purposes:
No one else can steal your GitHub OAuth credentials from code (pretty evident)
You'll have to create another oauth app (this is GitHub-specific by the way) with the authorization callback URL set to your site's hosted URL in GitHub because you'd be hosting your web app & if you use your old OAuth credentials, it would simply redirect the user to
http://localhost:3000/api/auth
instead ofhttps://your-hosted-app-url.vercel.app/api/auth
on authorization requests from the hosted version.
(This is why.env
files are used extensively for different environments!)
" oi oi oi oi oi.... why's there a NEXT_PUBLIC_
there? "
Oh, you caught me slippin' that one, eh!
As you know, adding NEXT_PUBLIC_
after the env file variable names tell NextJS that we wanna "expose" that env variable to the client side React app, Not adding this prefix in a variable means that we don't wanna use that variable or "expose" it on the client side.
This is where the role of the server-side API endpoint comes in,
You see, the GitHub client secret is like a password & the client id, you can think of it as a username to help GitHub identify your app.
You can show your username to the public but it wouldn't be preferable to publicly expose your password just like the username,
If we add NEXT_PUBLIC_
in front of a sensitive env file variable, NextJS while building your app bundle (i.e the final javascript file to be sent to the user's browser) will include that variable in the file source code too.
If you're patient enough to read through the javascript responses of a page, you can easily find it and someone with malicious intents can easily exploit this exposed variable!
However, you can still use the env variable without the NEXT_PUBLIC_
but only on the server side.
This is why we'll be using the server for all the communications with GitHub API which includes sending client secret along with our requests.
Honestly idk why I named this section "Enough talking Let's Code!" cuz we're still talking, but promise me, the next line will be code!
.
.
.
Hah! it wasn't π€£
Jokes apart, let's start by building the URL that GitHub requires our app to redirect users in order to start the authorization process.
From here, I'll be referring to GitHub's own docs for performing OAuth requests, you can check them out hereππ»
https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps
Step 1: Redirect User to Authorization URL
const oauthUri = `https://github.com/login/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_GH_CLIENT_ID}&scope=user:email,read:user&redirect_uri=http://localhost:3000/api/auth`
For the sake of not talking extra, I've hard-coded the redirect_uri
to http://localhost:3000/api/auth
that should actually be stored in a .env
file and changed according to the environments in which the app is being hosted and the associated authorization callback URL.
Coming to the scope
param of the URL, we've provided a list of scopes: read:user & user:email to it which means we want read access to the user's details (username, name, etc.) & their email.
The code looks something like this:
After running the app in dev mode & clicking the link "Login with GitHub" on the page, we are redirected to this page:
Have a close look at this page, it clearly describes an App named Test made by a GitHub user Kuvam Bhardwaj wants your GitHub profile data (email & other public info) and clearly states the access is read-only.
Refer to this url for more info on all available scopes
ππ»
https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps
Seeing this page is generally a big relief as we didn't mess up the setup in GitHub developer settings βπ»
Authorize the app & if you correctly put the URL in the authorization callback URL in GitHub settings, you should be redirected to the specified URL (/api/auth
)
Notice the URL address bar contains some kind of code
following a string of random characters,
This, ladies & gentlemen, is how victory looks, a string of hexadecimal characters π₯
Let's now create a file named auth.ts
under api
folder to actually handle this victory!
Step 2: Exchanging the Code for an Access token
After authorizing, GitHub redirects the user to our specified URL which is actually a server-side API endpoint.
This API endpoint will make a POST request to GitHub's OAuth API sending the obtained code
and client secret as the payload, in response, we'll probably receive an access_token
if the code
was valid.
On receiving the access_token
we can start making requests on behalf of the user, in this case, getting their email & public info.
If the scopes we previously specified were of editing user's personal data, followers management or even repo creation/deletion, we would be able to do that too with this access_token
though you might have to search the docs for finding a suitable API endpoint that does that.
You would get errors if you make requests to API endpoints that require additional scopes than you've specified.
Referring GitHub's API docs is the best way to know what endpoints require which scopes.
Or... just... refer to docs in general... they're helpful!
In our simple example, we'll simply exchange code
for access_token
, fetch the user's profile data with it, print it on the console and redirect the user to the homepage.
This could easily be scaled to do obvious things like:
save the user's data in a database after fetch
set a cookie to identify the user by their database ID
redirect to some dashboard or some other personalized page after authenticating them with cookies
Let us look at the code for exchanging the code
for acess_token
Ahem... please excuse my copilot prompts...
In the above code, when the user lands on /api/auth
we grab the code
embedded in the URL & send it in the POST request body along with other variables like client_id
& client_secret
to get back the access_token
from GitHub.
Headers in the fetch request, as hinted by their name, say to GitHub's API that we are sending a body in JSON format (Content-Type
) & we want a response in a JSON format (Accept
).
For some reason, GitHub defaults to sending a string looking something like this unless we explicitly provide the Accept
header in the POST request
"access_token=gho_16C7e42F292c6912E7710c838347Ae178B4a&scope=repo%2Cgist&token_type=bearer"
Our work is partially done now, we have the access_token
and just need to use this and get the user's profile details on their behalf.
Step 3: Use the Access token to Perform Actions on user's behalf
After getting access_token
we use it in the header as Authorization: Bearer access-token...
& request the GitHub API to grant us their profile data.
Let's now click on the "Login with GitHub" button on the index page again!
Generalizing GitHub-specific Patterns for other OAuth Providers
So, until now we've been very specific to GitHub, but if you refer to docs of other OAuth providers like LinkedIn or Google or Facebook,
You'll notice this pattern where:
You get your personalized client id & client secret
Redirect the user to a link with the client id, redirect uri & scopes or access permissions embedded in it
The user either authorizes or cancels the authorization request and you get the user redirected to your site with an error or some kind of code
You then exchange this code for an
access_token
Finally, you start making requests to the service on behalf of the user with the
access_token
If you've used any BaaS platform like firebase or supabase before, then you can distinctively point out what's happening!
For example here in supabase when I go ahead to setup Authentication with Social login via Google:
It asks me to enter my client id, and client secret & gives me a URL to add in redirect URIs in Google's OAuth setup process,
Supabase will manage the rest on its own, all the callbacks, redirecting users to your app, everything!
I might have to add that you still need to provide YOUR APP's URL so that supabase itself can redirect the user to your web app post-authentication.
Here's Google's OAuth setup screen for reference
And you might as well find the docs that list all the scopes for authorization along with all the minute details that are Google-specific!
See ya! βπ»
Thanks for reading!
If you liked the article, consider dropping some emojis β₯οΈπ₯π―β¨
But, if you loved it?
I post about the web every day on Twitter ππ» bhardwajkuvam
Consider giving me a follow there β₯οΈ