You see it everywhere: "login with < Google|Twitter|GitHub|Facebook|etc >". Personally I like it, A LOT. It's fast, easy, and saves me time. Who needs more passwords to remember? Heck, I used it to login to write this post.
Now it's time to find out how it works. Come with me, as we journey into the magical world of OAuth 2...
TLDR
OAuth 2 is easy to implement. There are just 2 steps: request a code and use that code to get a token. That's it. If you prefer reading this post in another language, I translated it to typescript.
Set up the client
I chose Google, but, since OAuth is a standard, it should be fairly similar for any provider. Here's how to set up an OAuth client with Google:
- Create a new project on Google dashboard
- Enable APIs for that project. You can enable ANY service Google has: Drive, Gmail, Sheets, Docs, Voice-to-text, etc. The basic one you'll need is the Google People API, which provides information about user profiles
- Create an OAuth client. This will be the client id/secret you need for requesting the oAuth token. First click 'Create Credentials' and then OAuth Client ID. It will prompt you to create a consent screen. Follow the prompts and then fill out the form like this. Make sure to set up the authorized domains and redirect domains. I'm using
https://localhost
, but you can use whatever you like, as long as it's HTTPS. It's easy to run HTTPS locally with this node package. - Copy the client id and secret for later use.
Get a Token
Now here's the fun part. It takes just 2 steps to get an OAuth token from Google:
Request an authorization code
The code is not THE token, it's called the authorization code and it's used to get the token later.
"That's dumb. Why not just send the token?" Good question. It used to be that way. It is called the Implicit Grant Type. It's a bad idea and generally not recommended any more (in some cases outright banned).
We'll be using the Authorization Code Grant Type. It takes one more step, but is more secure.
// GET request with the following params
{
code_challenge_method: 'S256',
scope: 'email profile', // tells google what info you want
access_type: 'offline',
response_type: 'code',
client_id: clientId, // clientID from step 1
redirect_uri: redirectUri, // page that handles token request
code_challenge: challengeToken, // hashed/encoded PKCE challenge
state: stateString, // random guid that will be passed back to you
}
// example
<a href="https://accounts.google.com/o/oauth2/v2/auth?code_challenge_method=S256&scope=email%20profile&access_type=offline&response_type=code&client_id=<client id>&redirect_uri=https://localhost/auth&code_challenge=o259Sjip6Cevgfe8RUw59jVO5z1mSzji_OuzIZFDTug&state=434595.10145617445">
Login with Google
</a>
The aforementioned code_challenge
parameter is from a method called PKCE. It stands for Proof Key for Code Exchange and is a security method to help make OAuth more secure. At its core, it's an hashed string that you send to the provider so it can verify your identity in the second step by sending the original string from which it was hashed. There's a real helpful node package that helps you generate PKCE challenges.
Request the OAuth Token
If all goes right on the first request, the provider will ask the user to login, generate the authorization code, and redirect to the uri specified in the redirect_uri
param. It will include 2 important URL params: code
and state
.
Code is the authorization code needed to request the OAuth token.
State is the initial state
param you sent in the last step. It's a simple mechanism to ensure the client can verify the identity of the server. If the state doesn't match, or isn't included, you can know not to trust that request.
// POST request with the following params
{
code: authCode, // authorization code from the provider
client_id: clientId, // id of the OAuth client
client_secret: clientSecret, // secret of the OAuth client
redirect_uri: redirectUri, // same from last request ¯\_(ツ)_/¯
grant_type: 'authorization_code',
code_verifier: codeVerifier, // raw PKCE token
}
// returns the following payload
{
access_token: <access token>, // this can be used to query APIs
refresh_token: <refresh token>, // can be used to get a new token
expires_in: <expiration in seconds>, // usually set to an hour
id_token: <id of the user>, // haven't really found a use for this
}
Use the Token!
You can then use the token to get data from Google on behalf of the user.
axios.get(
'https://www.googleapis.com/oauth2/v2/userinfo',
{
headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': `Bearer ${token}`
}
}
)
Google has a cool OAuth playground where you can try out all kinds of APIs.
That's it! You're done!
Resources
Here's a link to some of the resources I used to learn about this:
PKCE explanation: https://oauth.net/2/pkce/
PKCE node package: https://www.npmjs.com/package/pkce-challenge
Google OAuth Playground: https://developers.google.com/oauthplayground
OAuth2 Overview: https://aaronparecki.com/oauth-2-simplified/#web-server-apps
Google OAuth Walkthrough: https://developers.google.com/identity/protocols/OAuth2InstalledApp#obtainingaccesstokens
Sample GitHub repo: https://github.com/coleHafner/oauth-test/tree/parcel
Top comments (2)
How does this compare to using something like Firebase Authentication?
That's a great question. I'm not sure. Guess Firebase will have to be my next post...