Application security

Creating an API authenticated with OAuth 2 in Node.js

Ivan Dimov
January 28, 2018 by
Ivan Dimov

In the second part of the Securing Web APIs series, we are going to shed light on the OAuth authorization framework and we are going to build a simple API with OAuth authentication/server in Node.js.

If you missed the first part, you can find it here. In it, we discuss some of the basic methods to secure your Node.js API such as HTTP Basic Auth, HTTP Digest and we show some API conventions.

What is OAuth?

OAuth allows for identity delegation. Identity delegation allows a resource provider (such as Facebook) to be informed of the fact that a resource owner (a particular user in Facebook) allows a third-party (some application other than Facebook) to access and/or change the data belonging to the resource owner that is stored with the resource provider (such as allowing the third-party application to update the user's Facebook status or get a list with the user's friends). OAuth 2 is becoming the standard when it comes to the security of APIs and many famous web apps have already implemented this authorization mechanism – such as Facebook, LinkedIn, Google and PayPal.

11 courses, 8+ hours of training

11 courses, 8+ hours of training

Learn cybersecurity from Ted Harrington, the #1 best-selling author of "Hackable: How to Do Application Security Right."

OAuth 2.0 involves four grant types. Grant types indicate exactly how the third party is going to be authorized to access the resource owner's data stored with the resource provider. Most of the grant types allow the third-party application to access the user's data in the resource provider without that third party ever being aware of the credentials (login) of that user.

Our API will require an access token upon which third-party apps would be able to get random or specific quotes. Of course, this does not really need an OAuth authorization as the quotes do not have anything to do with the resource owner but it could have been a good candidate for OAuth if the users added the quotes to their account themselves and the third-party needed to view their quotes and process them in some way.

Grant types in OAuth 2

Authorization code

We have implemented this grant type in the API we will be building, so we will show live examples.

To get an authorization code the third-party has to redirect the user to the resource provider's website using the GET request method and pass three parameters: response_type=code,client_id=THE_CLIENT_ID_WITH_THE_RESOURCE_PROVIDER, and redirect_uri=URL_WHERE_THE_CODE_WILL_BE_RETURNED.

Therefore, if https://www.infosecinstitute.com wants to get an authorization code for our quote API it should do a GET request like this: http://localhost:9999/oauth/authorise/?response_type=code&client_id=TBrFXOaLFOlcghpA&redirect_uri=https://www.infosecinstitute.com

If the user who is redirected to our localhost:9999 from infosecinstitute is already logged in with the site in localhost:9999,then he will end up being asked to confirm the delegation and he will be redirected to https://www.infosecinstitute.com with a GET parameter called code where the authorization code will be passed. If he is not logged in, he would be redirected to the login page while the GET parameters (response_type,client_id,redirect_uri) remain intact and after he logs in and be asked to confirm that he authorizes the app to access his data he will finally be redirected to the URL provided in redirect_uri with the code. InfoSec Institute can then use the code in the GET parameter to get an access token and use the quote API to get the quotes of that user and process them in any way it likes. Here is a sample result/response from our /oauth/authorise/ call if the redirect_uri is set to https://www.infosecinstitute.com: https://www.infosecinstitute.com/?code=f1756264ecf620d679aa625467908aa91fa59dcb.

Then, the third-party app could use what is in the GET code parameter to get an access token by providing the client id. The client secret that the third-party application received when registering itself as an app that works with the resource provider, the code itself and a grant type of authorization_code in a POST request to the resource provider.

Here is a request that we use in our API to get an access token from a received code.

PS C:UsersIvan> curl --noproxy localhost:9999 localhost:9999/oauth/token --data "client_id=pE1Qz7LSOzTPSbP4&client_secret=KaXS2LfnOUxXjTq5iikoz3LIgnPiM8&grant_type=authorization_code&code=3aa27400f59bddf55e39a396fa561f6d2a0f3277&redirect_uri=http://www.dimoff.biz"

We get the following JSON response:

{"token_type":"bearer","access_token":"31ae0ca214abbc4a303136dba375597f44933a69","expires_in":86400}

You can see the POST request to localhost:9999/oauth/token with those parameters returns to us the access token that we can use to work with the user's data and get the user's quotes.

How to use an access token with a resource provider?

We can use an access token in two ways: either add it as a GET parameter to our API calls like this: curl "http://localhost:9999/api/random/?access_token=31ae0ca214abbc4a303136dba375597f44933a69"

This will return a JSON-encoded string with a quote and its movie title in our sample API: {"quote":"[Elisabeth comments on how fast Patrick had begun sleeping with Holly when arn guy comes out of her bedroom]rnPatrick Highsmith: What was that you were saying about 6 months ofrn suffering?rnElisabeth: OK, so I'm a slut, you're a slut, who wants coffee?rnrnrn","movieTitle":" Doppelganger (1993)"}

If you want the access token not to be revealed in the URL and shown if the link is shared or in the browser's history, you can do a request with Authorization header set to "Authorization: Bearer ACCESS_TOKEN".

Here is an example of passing the token in a header using cURL:

PS C:UsersIvan> curl "http://localhost:9999/api/random/" -H "Authorization: Bearer 31ae0ca214abbc4a303136dba375597f44933a69"

Password credentials

Using this grant type, applications can directly get an access token but would have to know the username and the password of the resource owner.

The third-party app has to make a POST request to the OAuth endpoint (in our case /oauth/token) with a HTTP Basic Auth header with contents being the app's client id and client secret encoded in base64 and separated by a : ("Authorization: Basic CLIENT_ID:CLIENT_SECRET"). The app must also provide some POST data with the request – a grant type of password and the username and password of the resource owner who is delegating access.

Here is how we get an access token in our sample API with cURL:

PS C:UsersIvan> curl localhost:9999/oauth/token -H "Authorization: Basic cEUxUXo3TFNPelRQU2JQNDpLYVhTMkxmbk9VeFhqVHE1aWlrb3ozTElnblBpTTg=" --data "grant_type=password&username=tester2&password=tester2"

A sample response would be:

{"token_type":"bearer","access_token":"fc415000a8cf06ad2688b1421fa64f6ab24c44de","expires_in":86400}

How do we secure our API with OAuth in Node.js

We have to provide a middleware before our API's endpoint route is reached that would check if a valid access token is provided. Here is how we have achieved this with node-oauth2-server:

app.get("/api/random", app.oauth.authorise(), routes.getQuoteRandom);

app.get("/api/id/:id", app.oauth.authorise(), routes.getQuoteById);

If we want some points of our API to be public we just have to remove the app.oauth.authorise() middleware.

OAuth and security

If our app has received an access token for the user, tester2 the token would grant the app access only to the data of that user.

This token would only be valid for the user the access token was generated for (through his userId) and the resource provider would know all about the third-party application to which the user has delegated access. A UI could be built where users can reject access to third parties to which they have given access (such as Facebook - https://www.facebook.com/settings?tab=applications).

A possible drawback is that access tokens could be brute-forced (for example, the bearer token is an arbitrary string and brute-forcing it is possible depending on its length amongst other measures that would discover brute-forcing attempts).

The most common OAuth 2 vulnerability

This vulnerability allows provider accounts to be linked to the client accounts of victims and involves CSRF.

The vulnerability allows attackers to hijack client accounts. For the vulnerability to be there, the client app has to support logging in with the OAuth provider and the ability to add OAuth provider logins in its settings. The attacker starts the authorization process by adding OAuth provider login without visiting the callback that he is attempted to be sent to (by blocking the redirect). Then, the victim has to use that callback's URL (either directly or through an image or anchor somewhere) and if the victim is logged in in the vulnerable website the attack could be successful and the attacker only has to log in with that OAuth provider and he will be logged in directly to the victim's account on that site.

To read more about the vulnerability visit: http://homakov.blogspot.com/2012/07/saferweb-most-common-oauth2.html

Leaking authorization code

The OAuth documentation pinpoints that the redirect_uri with which the client application registered must match with the one used to obtain an access token. If the redirect_uri is not checked then a malicious user can insert an image or anchor on the client's website (either through a vulnerability such as XSS or perfectly legal if it allows users to share links and images somewhere) with a URL pointing to the attacker's website. Then he can construct a URL that requests for an authorization code in the resource provider's domain with a redirect_uri pointing to an URL with the embedded by the attacker image/link. Whenever a user accesses the authorization code request URL, the attacker could see the authorization code through the script behind his embedded image/link via the Referer header (which will include the GET parameter called code and its value) because the redirect_uri would make a call to the attacker's image in order to load it.

To protect yourself against this account hijacking vulnerability, you should avoid flexible redirect urls (our sample app does that) or store the redirect url for each code you issue in your database and when you create an access token with that code – check if the redirect url matches the redirect url that was given when the code was first created.

The sample server we have created provides the following error when the redirect_uri provided when getting an authorization code does not match the one with which the application first registered: OAuth2Error: redirect_uri does not match

For a full cheat sheet with OAuth 2 vulnerabilities visit: http://www.oauthsecurity.com/

Authorization code implementation in our API

Our API depends on Node.js with Express, body and cookie parser, session, mongojs and mongodb for persistence and the node oauth2 server to facilitate the creation of the OAuth server.

We initialize our server with:

app.oauth = oauthserver({

model: require('./model.js'), // See below for specification

grants: ['password', 'authorization_code'],

debug: true,

accessTokenLifetime: 60 * 60 * 24,

clientIdRegex: '^[A-Za-z0-9-_^]{5,30}$'

});

We declare the model by requiring our custom module. We specify that we want to use two grant types – password and authorization code, set the app to development version ( debug: true), set the access token to last for 24 hours and declare that our client ids would consist of lower and uppercase Latin characters, numbers, -, _, ^ and would be between 5 and 30 characters in length.

The model in our MongoDB implementation uses the following collections:

collections = ['oauth_access_tokens', 'oauth_clients', "oauth_refresh_tokens", "users", 'oauth_codes'];

oauth_clients contains the third-party apps that are going to get data from resource owners (their clientId,secretId and redirectUri), oauth_access_tokens contains the currently valid access tokens that were generated along with the client_id whose token this is, the user_id for which the token is valid and the expiry time of the token, users contains the username, the password and an id of the ordinary users and oauth_codes contains the authorization codes along with the client id and the user id associated with that code.

The documentation for node-oauth2-server could be found at: https://www.npmjs.com/package/node-oauth2-server.

All dependencies could be installed with the node package manager (npm).

To enable the authorization code grant type, we add the saveAuthCode and getAuthCode to our OAuth model. getAuthCode finds a particular code in our database collection (when users attempt to use one) and saveAuthCode saves the code along with the client id (to identify the third-party application app) and user id (to identify the user whose data would be accessed by the third-party application) that the code belongs to.

In our routes (routes.js), we have allowed everybody to register as a user via getRegister and postRegister (the former shows the registration form while the latter adds the user to our database collection) and as a client (third-party app) through getClientRegister and postClientRegister (the former shows a form which only asks for a redirect URI while the latter generates a random client id and client secret and saves that data, along with the provided redirect URI, to a database collection.

Whenever a user issues a POST or a GET request to /oauth/authorise, we check if there is an existing session and if there is not – we ask him to login before getting the code. We presume the user allowed access to the app by setting allow to "yes" (an alternative should be introduced for live APIs that involves some sort of a prompt showing the application's name and asking the user to confirm that he agrees the third-party application to use his data):

app.get('/oauth/authorise', function (req, res, next) {

if (!req.session.user) {

// If they aren't logged in, send them to your own login implementation

return res.redirect('/login?redirect=' + req.path + '&client_id=' +

req.query.client_id + '&redirect_uri=' + req.query.redirect_uri);

}

//TODO: SHOW THEM "do you authorise xyz app to access your content?" page

req.body.allow = "yes";

next();

}, app.oauth.authCodeGrant(function (req, next) {

// The first param should to indicate an error

// The second param should a bool to indicate if the user did authorise the app

// The third param should for the user/uid (only used for passing to saveAuthCode)

next(null, req.body.allow === 'yes', req.session.user, req.session.user);

}));

If the user is logged in – we redirect him to the provided website in redirect_uri with the code in the query string.

11 courses, 8+ hours of training

11 courses, 8+ hours of training

Learn cybersecurity from Ted Harrington, the #1 best-selling author of "Hackable: How to Do Application Security Right."

Conclusion

Creating an OAuth 2 server is not a task that should be taken lightly. There are many security loopholes that could be exploited, we have covered some of them and provided links for further examination. However, despite any possible vulnerabilities OAuth 2 is a great way to provide safe identity delegation between the users and the third party developers involved with your application. This is why we suggest that you catch up with the example OAuth 2 server/API we have set up and get a good idea of how OAuth2 ticks.

Sources

Ivan Dimov
Ivan Dimov

Ivan is a student of IT and Information Security. He is currently working toward a Master's degree in the field of Informatics in Sweden. He is also a freelance web developer engaged in both front-end and back-end coding and a tech writer. Whenever he is not in front of an Interned-enabled device, he is probably reading a print book or traveling.