Update 2/29/16: These code examples have been updated to reflect the 3.0 release of the express-stormpath integration.

When you build a REST API, creating the infrastructure required to secure an API with keys, OAuth tokens, and scopes can be tedious, risky and time-consuming. Fortunately, Stormpath just added API key management to our express-stormpath package. Now, API and web app developers using express.js can generate and manage API Keys and OAuth tokens as well as lookup and secure developer accounts – all without custom OAuth code.

What We’re Building

This post will walk you through building an express application that lets you:

Create a new account (register) with email and password.

Log into your new account (login) with email and password.

Display a dashboard page once you’ve logged in, that is only accessible to registered users.

Automatically generate and show an API Key for a logged in user on the dashboard.

Make a REST call using Basic authentication.

Generate an OAuth token with scope.

Make a REST call using Bearer authentication.

Allow a logged in user to log out of their account (logout).

The API I built is a simple one: it returns the weather for a requested city in Fahrenheit.

Before We Start…

Note the directory structure of the github repository for this project. All server-side logic (which this post focuses on) will be in server.js .

Login

User registration and login are built into express-stormpath, and a good place to start with any app.

Let’s take a look at the code that makes this happen. We first import the necessary packages (all code in this post goes into the root node file, in my case /server.js) :

var express = require('express'); var server = express(); var stormpath = require('express-stormpath'); // Now, we setup the server to use Stormpath. server.use(stormpath.init(server, { application: { href: process.env['STORMPATH_APPLICATION_HREF'] }, web: { login: { nextUri: '/dashboard', }, oauth2: { client_credentials: { accessToken: { ttl: 3600 } } } } })); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var express = require ( 'express' ) ; var server = express ( ) ; var stormpath = require ( 'express-stormpath' ) ; // Now, we setup the server to use Stormpath. server . use ( stormpath . init ( server , { application : { href : process . env [ 'STORMPATH_APPLICATION_HREF' ] } , web : { login : { nextUri : '/dashboard' , } , oauth2 : { client_credentials : { accessToken : { ttl : 3600 } } } } } ) ) ;

The application field points Stormpath to the right application for the project. Users will be pointed to the nextUri after logging in or creating an account. As for the Oauth fields, we will get back to those a little later. It is important to avoid storing your API Key, Application Href, and Secret Keys (used for user sessions) in plain text in your code. Instead, export these as environment variables and access them in server.js by doing process.env['name_of_env_var'] . Stormpath-express is capable of reading the environment variables itself, so exporting them to your system is enough; however, above I show how to manually set the stormpath-express variables in code.

Once a user goes to our site (the root of the site or ‘/’), we need to redirect them to the custom login page provided by stormpath-express, which by default lives at /login .

server.get('/', function(req, res) { res.redirect(302, '/login'); }); 1 2 3 4 server . get ( '/' , function ( req , res ) { res . redirect ( 302 , '/login' ) ; } ) ;

Now that we have login and account creation out of the way, let’s use API Keys to protect our weather API.

API Key Generation

First, we need to give the user an API key to use. This way, any REST endpoint is protected and accessible only to users who possess a valid API Key and Secret. Once a user logs in or creates an account, they will go directly to the application dashboard, where an API Key is automatically generated and displayed.

Let’s see what the code looks like:

server.get('/dashboard', stormpath.loginRequired, function(req, res) { res.locals.user.getApiKeys(function(err, collectionResult) { if (collectionResult.items.length === 0) { res.locals.user.createApiKey(function(err, apiKey) { res.locals.apiKeyId = apiKey.id; res.locals.apiKeySecret = apiKey.secret; res.locals.username = res.locals.user.username; res.render('dashboard.ejs'); }); } else { collectionResult.each(function(apiKey) { res.locals.apiKeyId = apiKey.id; res.locals.apiKeySecret = apiKey.secret; res.locals.username = res.locals.user.username; res.render('dashboard.ejs'); }); } }) }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 server . get ( '/dashboard' , stormpath . loginRequired , function ( req , res ) { res . locals . user . getApiKeys ( function ( err , collectionResult ) { if ( collectionResult . items . length === 0 ) { res . locals . user . createApiKey ( function ( err , apiKey ) { res . locals . apiKeyId = apiKey . id ; res . locals . apiKeySecret = apiKey . secret ; res . locals . username = res . locals . user . username ; res . render ( 'dashboard.ejs' ) ; } ) ; } else { collectionResult . each ( function ( apiKey ) { res . locals . apiKeyId = apiKey . id ; res . locals . apiKeySecret = apiKey . secret ; res . locals . username = res . locals . user . username ; res . render ( 'dashboard.ejs' ) ; } ) ; } } ) } ) ;

By calling res.locals.user.getApiKeys we ask Stormpath to return a collection of an account’s API Keys. In the if statement, we check if the account has any API Keys. If not, Stormpath generates one and returns it to the client. In the else statement, where an API Key has already been generated, Stormpath returns the first API Key available.

Making a REST Call With Basic Authentication

Now the user is logged in and has access using the API Key Id and Secret. Let’s have them make an API call.

In this sample application, the REST endpoint returns a floating point number, representing the weather in the requested city. For example if a GET request is made to /weather/London , a floating point number with one digit after the decimal is returned to the client representing the weather in London. Only one endpoint is available in the form of weather/{{city}} , where city can be any one of the four cities provided by the radio buttons.

First, the client has to Base64 encode the key:secret pair and send this to the server as the authorization header. In angular.js, the HTTP request would look something like this:

$http({ method: 'GET', url: '/weather/' + $scope.city, headers: { 'Authorization': 'Basic ' + sharedProperties.getEncodedAuth() } }).success(function(data, status, headers, config) { $scope.temp = data + ' F'; $scope.myCity = $scope.city; }).error(function(data, status, headers, config) { $window.alert('Error'); }); 1 2 3 4 5 6 7 8 9 10 11 $ http ( { method : 'GET' , url : '/weather/' + $ scope . city , headers : { 'Authorization' : 'Basic ' + sharedProperties . getEncodedAuth ( ) } } ) . success ( function ( data , status , headers , config ) { $ scope . temp = data + ' F' ; $ scope . myCity = $ scope . city ; } ) . error ( function ( data , status , headers , config ) { $ window . alert ( 'Error' ) ; } ) ;

In this HTTP request, we specify the desired city in the url: /weather/{{city}} and add our Base64 encoded API key as the authorization header.

Now lets take a look at what happens on the server side, where this request gets processed:

server.get('/weather/:city', stormpath.apiAuthenticationRequired, function(req, res) { function getWeather() { console.log('Getting weather for ' + req.params.city); var url = "'ttp://api.openweathermap.org/data/2.5/weather?q=' + req.params.city; var data = ''; http.get(url, function(myRes) { myRes.on('data', function(chunk) { data += chunk; }); myRes.on('end', function() { callback(data); }); }).on('error', function() { console.log('Error getting data.'); }); } function callback(finalData) { var json = JSON.parse(finalData); // Convert to Farenheight: var farenheight = Math.round((((parseFloat(json.main.temp) - 273.15) * 1.8) + 32) * 10) / 10; res.status(200).json(farenheight); } if (req.headers.authorization.indexOf('Basic') !== 0) { return res.status(403).end(); } getWeather(); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 server . get ( '/weather/:city' , stormpath . apiAuthenticationRequired , function ( req , res ) { function getWeather ( ) { console . log ( 'Getting weather for ' + req . params . city ) ; var url = " 'ttp://api.openweathermap.org/data/2.5/weather?q=' + req . params . city ; var data = '' ; http . get ( url , function ( myRes ) { myRes . on ( 'data' , function ( chunk ) { data += chunk ; } ) ; myRes . on ( 'end' , function ( ) { callback ( data ) ; } ) ; } ) . on ( 'error' , function ( ) { console . log ( 'Error getting data.' ) ; } ) ; } function callback ( finalData ) { var json = JSON . parse ( finalData ) ; // Convert to Farenheight: var farenheight = Math . round ( ( ( ( parseFloat ( json . main . temp ) - 273.15 ) * 1.8 ) + 32 ) * 10 ) / 10 ; res . status ( 200 ) . json ( farenheight ) ; } if ( req . headers . authorization . indexOf ( 'Basic' ) ! == 0 ) { return res . status ( 403 ) . end ( ) ; } getWeather ( ) ; } ) ;

First, notice the stormpath.apiAuthenticationRequired call that precedes the callback function of our route. This function verifies that the authorization credentials sent over are legitimate. If they are not, the server will return a 401 Unauthorized error. Assuming the credentials are correct, the server is then allowed to return the weather of the desired city.

Here is how the same request can be made with CURL:

$ curl --user '[API_KEY]:[API_SECRET]' http://localhost:8080/weather/London 1 2 $ curl -- user '[API_KEY]:[API_SECRET]' http : //localhost:8080/weather/London

If authentication is successful you will get back a floating point number representing the weather in London. If it is not, you will see: {"error":"Invalid API credentials."} .

Generating an OAuth Token with Scope

Basic Authentication is acceptable for a few use cases, but we strongly recommend you use OAuth if security is important to your API. By using OAuth, making requests to protected endpoints does not expose the API Key Id and Secret. It also gives a developer the ability to only give access to certain scopes of the endpoint the user is trying to access. This is compared to authenticating with API keys, which gives access to the entire endpoint.

By checking the desired cities and clicking Get Oauth , the user gets a token which can now be used to target the REST endpoint. What exactly happened on the server side to generate this Oauth Token? Let’s look at the server setup code one more time:

server.use(stormpath.init(server, { application: { href: process.env['STORMPATH_APPLICATION_HREF'] }, web: { login: { nextUri: '/dashboard', }, oauth2: { client_credentials: { accessToken: { ttl: 3600 } } } } })); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 server . use ( stormpath . init ( server , { application : { href : process . env [ 'STORMPATH_APPLICATION_HREF' ] } , web : { login : { nextUri : '/dashboard' , } , oauth2 : { client_credentials : { accessToken : { ttl : 3600 } } } } } ) ) ;

With this, a POST request sent to that URL will check for API Key credentials and return a Token that is valid for 1 hour (by default). This POST request also needs to have a form parameter “grant_type” with the value set as the requested scope. Here is the request in angular.js:

myData = $.param({grant_type: "client_credentials", scope: scopeData}); $http({ method: 'POST', url: '/oauth/token', headers: { 'Authorization': 'Basic ' + sharedProperties.getEncodedAuth(), 'Content-Type': 'application/x-www-form-urlencoded' }, data : myData }).success(function(data, status, headers, config) { var oauthToken = data.access_token; $scope.oauthToken = oauthToken; }).error(function(data, status, headers, config) { $window.alert('Error'); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 myData = $ . param ( { grant_type : "client_credentials" , scope : scopeData } ) ; $ http ( { method : 'POST' , url : '/oauth/token' , headers : { 'Authorization' : 'Basic ' + sharedProperties . getEncodedAuth ( ) , 'Content-Type' : 'application/x-www-form-urlencoded' } , data : myData } ) . success ( function ( data , status , headers , config ) { var oauthToken = data . access_token ; $ scope . oauthToken = oauthToken ; } ) . error ( function ( data , status , headers , config ) { $ window . alert ( 'Error' ) ; } ) ;

Making a REST Call Using the OAuth Token

In order to hit the REST endpoint using Oauth, we must send our token to the weather/{{city}} endpoint using Bearer authentication:

$http({ method: 'GET', url: '/weather/' + $scope.city, headers: { 'Authorization': 'Bearer ' + sharedProperties.getOauthToken() } }).success(function(data, status, headers, config) { $scope.temp = data + ' F'; $scope.myCity = $scope.city; }).error(function(data, status, headers, config) { $window.alert('Permission Denied!'); }); 1 2 3 4 5 6 7 8 9 10 11 $ http ( { method : 'GET' , url : '/weather/' + $ scope . city , headers : { 'Authorization' : 'Bearer ' + sharedProperties . getOauthToken ( ) } } ) . success ( function ( data , status , headers , config ) { $ scope . temp = data + ' F' ; $ scope . myCity = $ scope . city ; } ) . error ( function ( data , status , headers , config ) { $ window . alert ( 'Permission Denied!' ) ; } ) ;

Now, on the server side we can add the logic for Bearer authentication, and parse our requested scopes.

server.get('/weather/:city', stormpath.apiAuthenticationRequired, function(req, res) { if (req.headers.authorization.indexOf('Basic') === 0) { getWeather(); } else if (req.headers.authorization.indexOf('Bearer') === 0) { var requestedCity = req.params.city.replace(/\s+/g, ''); if (res.locals.permissions.indexOf(requestedCity) >= 0) { getWeather(); } else { res.status(403).end(); } } else { res.status(403).end(); } function getWeather() { console.log('Getting weather for ' + req.params.city); var url = 'http://api.openweathermap.org/data/2.5/weather?q=' + req.params.city; var data = ''; http.get(url, function(myRes) { myRes.on('data', function(chunk) { data += chunk; }); myRes.on('end', function() { callback(data); }); }).on('error', function() { console.log('Error getting data.'); }); } function callback(finalData) { var json = JSON.parse(finalData); console.log(json.main.temp); // Convert to Farenheight: var farenheight = Math.round((((parseFloat(json.main.temp) - 273.15) * 1.8) + 32) * 10) / 10; res.status(200).json(farenheight); } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 server . get ( '/weather/:city' , stormpath . apiAuthenticationRequired , function ( req , res ) { if ( req . headers . authorization . indexOf ( 'Basic' ) === 0 ) { getWeather ( ) ; } else if ( req . headers . authorization . indexOf ( 'Bearer' ) === 0 ) { var requestedCity = req . params . city . replace ( / \ s +/ g , '' ) ; if ( res . locals . permissions . indexOf ( requestedCity ) > = 0 ) { getWeather ( ) ; } else { res . status ( 403 ) . end ( ) ; } } else { res . status ( 403 ) . end ( ) ; } function getWeather ( ) { console . log ( 'Getting weather for ' + req . params . city ) ; var url = 'http://api.openweathermap.org/data/2.5/weather?q=' + req . params . city ; var data = '' ; http . get ( url , function ( myRes ) { myRes . on ( 'data' , function ( chunk ) { data += chunk ; } ) ; myRes . on ( 'end' , function ( ) { callback ( data ) ; } ) ; } ) . on ( 'error' , function ( ) { console . log ( 'Error getting data.' ) ; } ) ; } function callback ( finalData ) { var json = JSON . parse ( finalData ) ; console . log ( json . main . temp ) ; // Convert to Farenheight: var farenheight = Math . round ( ( ( ( parseFloat ( json . main . temp ) - 273.15 ) * 1.8 ) + 32 ) * 10 ) / 10 ; res . status ( 200 ) . json ( farenheight ) ; } } ) ;

The requested scopes live inside the res.locals.permissions object and we can search it to see if the city we want the weather for is permitted for us. If so, the server will proceed to return the weather; if not, a 403 is returned. Compared to a 401 error, which is stands for an unauthorized request, a 403 represents a forbidden request.

London was part of the scopes in the Oauth Token so getting its weather is no problem:

Berlin on the other hand was not, so the weather is not given and an error is returned instead:

Conclusion

Node.js and the stormpath-express package make it easy to generate and manage API Key-based authentication in your webapp or API. If you’d like to see more code and even run this application yourself, check out the source code and let us know what you think!

Also, read our blog post on using Node.js for REST APIs in mobile apps for Android and iOS.