OAuth2 is an authentication protocol that is used to authenticate and authorize users in an application by using another service provider.
This post will go through how to build a Node.js application to implement the OAuth2 protocol.
If you just want to see the code, you can view it on Github
How OAuth2 Works
Let’s take a brief look at the OAuth protocol before we jump into implementation.
If you’ve ever seen a dialog like this, then you’ve probably used OAuth before:
Here, we are trying to login to Gitlab using Github to authenticate.
There are three parties in any OAuth mechanism:
- The client - The person, or user who is trying to log in
- The consumer - The application that the client wants to log into (which is Gitlab in this example)
- The service provider - The external application that authenticates the users identity. (which is Github in this example)
In this post, we’ll create a Node.js HTTP server (consumer) that uses Github’s OAuth2 API (service provider) to authenticate the user (client).
Let’s look at an overview of how this would work in practice.
Let’s go into the details of how to implement each part:
Creating the Clients Landing Page
Lets create the first part of the application, which is the landing page. This will be a basic HTML page, with a link that the user can click on to authenticate with Github.
We can create a new file, public/index.html
:
<!DOCTYPE html>
<html>
<body>
<a
href="https://github.com/login/oauth/authorize?client_id=myclientid123&redirect_uri=http://localhost:8080/oauth/redirect"
>
Login with github
</a>
</body>
</html>
The link URL has three key parts:
https//github.com/login/oauth/authorize
is the OAuth gateway for Github’s OAuth flow. All OAuth providers have a gateway URL that you have to send the user to in order to proceed.client_id=myclientid123
- this specifies the client ID of the application. This ID will tell Github about the identity of the consumer who is trying to use their OAuth service.OAuth service providers have portal in which you can register your consumer. On registration, you will receive a client ID (which we are using here as
myclientid123
), and a client secret (which we will use later on). For Github, the portal to register new applications can be found on https://github.com/settings/applications/new.redirect_uri=http://localhost:8080/oauth/redirect
- specifies the URL to redirect to with the request token, once the user has been authenticated by the service provider. Normally, you will have to set this value on the registration portal as well, to prevent anyone from setting malicious callback URLs.
Next, we need to create the HTTP server to serve the index.html
file we just made.
The following code would go into a new file index.js
:
// Import the express lirbary
const express = require("express");
// Create a new express application and use
// the express static middleware, to serve all files
// inside the public directory
const app = express();
app.use(express.static(__dirname + "/public"));
// Start the server on port 8080
app.listen(8080);
We’ll use the express library to implement the server in this example
You can start the server by executing node index.js
and visit http://localhost:8080
: You will see the landing page we just made.
Once you click on the “Login with github” link, you will be redirected to the familiar OAuth page to register with Github. However, once you authenticate, you will be redirected to http://localhost:8080/oauth/redirect
, which will lead to a 404 page on the server.
Let’s implement it now:
Implementing the Redirect Route
Once the user authenticates with Github, they get redirected to the redirect URL that was specified earlier.
The service provider also adds a request token along with the url. In this case, Github adds this as the code
parameter, so the redirect URL will actually be something like http://localhost:8080/oauth/redirect?code=mycode123
, where mycode123
is the request token.
We need this request token and our client secret to get the access token, which is the token that is actually used to get information about the user. We get this access token by making a POST
HTTP call to https://github.com/login/oauth/access_token
along with the mentioned information.
You can view the documentation page for the details of the information Github provides to the redirect URL, and the information we need for provide with the
POST /login/oauth/access_token
HTTP call.
Let’s add the /oauth/redirect
route to the index.js
file:
const express = require("express");
// Import the axios library, to make HTTP requests
const axios = require("axios");
// This is the client ID and client secret that you obtained
// while registering the application
const clientID = "<your client id>";
const clientSecret = "<your client secret>";
const app = express();
// Declare the redirect route
app.get("/oauth/redirect", (req, res) => {
// The req.query object has the query params that
// were sent to this route. We want the `code` param
const requestToken = req.query.code;
axios({
// make a POST request
method: "post",
// to the Github authentication API, with the client ID, client secret
// and request token
url: `https://github.com/login/oauth/access_token?client_id=${clientID}&client_secret=${clientSecret}&code=${requestToken}`,
// Set the content type header, so that we get the response in JSOn
headers: {
accept: "application/json",
},
}).then((response) => {
// Once we get the response, extract the access token from
// the response body
const accessToken = response.data.access_token;
// redirect the user to the welcome page, along with the access token
res.redirect(`/welcome.html?access_token=${accessToken}`);
});
});
app.use(express.static(__dirname + "/public"));
app.listen(8080);
Now the redirect URL is functional, and will redirect the user to the welcome page, along with the access token.
Redirecting to the Clients Welcome Page
The welcome page is the page we show the user after they have logged in. Now that we have the users access token, we can obtain their account information on their behalf as authorized Github users.
For a list of all APIs available, you can see the Github API Documentation
We will be using the /user
API to get basic info about the user and say hi to them on our welcome page. Create a new file public/welcome.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Hello</title>
</head>
<body></body>
<script>
// We can get the token from the "access_token" query
// param, available in the browsers "location" global
const query = window.location.search.substring(1);
const token = query.split("access_token=")[1];
// Call the user info API using the fetch browser library
fetch("https://api.github.com/user", {
headers: {
// This header informs the Github API about the API version
Accept: "application/vnd.github.v3+json",
// Include the token in the Authorization header
Authorization: "token " + token,
},
})
// Parse the response as JSON
.then((res) => res.json())
.then((res) => {
// Once we get the response (which has many fields)
// Documented here: https://developer.github.com/v3/users/#get-the-authenticated-user
// Write "Welcome <user name>" to the documents body
const nameNode = document.createTextNode(`Welcome, ${res.name}`);
document.body.appendChild(nameNode);
});
</script>
</html>
With the addition of the welcome page, our OAuth implementation is now complete!
Once the app starts, we can visit http://localhost:8080/
, authorize with Github, and end up on the welcome page, which displays the greeting. My name on my github profile is “Soham Kamani”, so the weclome page will display Welcome, Soham Kamani
once I login.
The source code, and instructions on how to run it can be found here
Additional Security Measures
Although this post demonstrated the basics of OAuth2, there is a lot more that can be done to further secure your application.
Session Tokens
In this example, we passed the access token to the client so that it can make requests as the authorized user. To make your app more secure, the access token should not be passed directly to the user. Instead, create a session token that is sent to the user as a cookie.
The app will maintain a mapping of the session tokens to access tokens on the server side (most likely a database).
Instead of making requests to github, the user will make requests to the node server (with the session token), which will in turn use the provided session token to look up the access token and make the request to github on the server side.
I have written more about sessions and cookies here.
OAuth Query State
While sending the user to the authorization URL, there is a provision to provide a value for a query parameter called state
. The value of this should be a random un-guessable string provided by the application.
When github calls the redirect url, it will attach this state variable to the request params. The new URL would now look like:
https://github.com/login/oauth/authorize?client_id=myclientid123&redirect_uri=http://localhost:8080/oauth/redirect&state=somerandomstring
The application can now compare this value with the value it originally generated. If they are not the same, that means the request came from some third party, and should be rejected.
This helps protect our application agains cross site request forgery attacks.