DEV Community

Cover image for 3 Steps to OAuth (with Code & Examples!)
Kuvam Bhardwaj
Kuvam Bhardwaj

Posted on

3 Steps to OAuth (with Code & Examples!)

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

dev.to login page

This is DEV's login page ☝🏻

The buttons you see here - "Continue with Apple / Forem / GitHub / Twitter" use The OAuth Flow to redirect you to a third-party service that will identify you for Dev.to (while you allow yourself to be identified by this service & give read access of your profile to Dev.to) and this service will then redirect you back to Dev.to'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:

  1. Your app

  2. The user

  3. 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
Enter fullscreen mode Exit fullscreen mode

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:

  1. No one else can steal your GitHub OAuth credentials from code (pretty evident)

  2. 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 of https://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`
Enter fullscreen mode Exit fullscreen mode

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:

Image description

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"
Enter fullscreen mode Exit fullscreen mode

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!

touching myself ryan reynolds GIF by Deadpool's Fun Sack

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:

  1. You get your personalized client id & client secret

  2. Redirect the user to a link with the client id, redirect uri & scopes or access permissions embedded in it

  3. 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

  4. You then exchange this code for an access_token

  5. 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 β™₯️

Top comments (0)