Standalone server wich implement OAuth 2.0 spec over express js, it supports the following grants:
- client_credentials
- refresh_token
- authorization_code(In development)
This server contains the following features:
- Database entities(sequelize based) wich allow oauth 2 protocol execution in a transparent way
- Express's api to control the server main entities
- An authorization endpoint based on Oauth 2.0 RFC
- An authorization middleware to be implemented on your expressjs routers
Entity | Description |
---|---|
oauth_suscriber | Represents a company or a mayor figure which is able to create clients in your oauth server, without a suscriber no client or token can be issued |
oauth_client | Represents a single application wich can request access tokens to a grant type(grant types supported are inherited by the suscriber) |
api_accounts | Represents a single account for a oauth client, it enables the client the ability to use a grant_type called client_credentials |
refresh_token | Stores the information for temporary credential used to get tokens from grant_type refresh_token |
access_token | Stores the credentials granted by the server with the corresponding client_id, it has a limited scope and life time, once expired a token is not usable anymore |
authorization_code | Stores the information for temporary credential used to get token from grant type authorization_code |
The authorizeResource middleware implemented by your resources will contain the following validations:
-
extractToken Validates the existence of an access token in the authorization header or in access_token query parameter
-
checkToken: Validates the token, searches in the cache for the token if does not exists it will look for it in the persistence, if none exists an error will be thrown, otherwise it will be checked if the token expired, if expired an error will be thrown otherwise the validation will continue.
-
checkScope: Validates if the scope of the current request is available in the token scopes if is not in the permissions an error will be thrown, otherwise the validation will be finished.
-
sessionHandler: A middleware wich enable two methods on the req object:
-
getSessionData: Gets the session data from the current access token given a key, if the property does not exists on the token data it will return null
-
setSessionData: Sets and synchronizes data from the current token for a given key with a given value, it will sync the value in token data on booth sources(memcached & database persistence)
-
Here are some useful oauth2 concepts used in this module, some of them are concepts from the RFC itself and it will help you out as reference from now on.
-
OAuth Client(OAuthClient): Represents a client application wich will request throught a grant type permissions to the server on the behalf of an authenticated user. OAuth clients represents an application that can be created by the service provider or by third party(like a facebook app for example, any facebook app represents an oauth client wich will request access to the server on the behalf of a certain profile owner).
-
Authorization Grant( Grant type): An authorization grant is a credential wich represent the resource owner authorization(to access its protected resources), this credential is used by the oauth client to obtain an access token. There are serveral grant types, the rfc ackwoledges the following:
-
authorization_code: The authorization code is obtained using the authorization server as intermediary between the client and the resource owner(end user), the client redirect the resource owner using the browser requesting permission to access(this is known as authorization dialog). Granting the permission will result on a redirection from the dialog to the client's application with an authorization code.
-
password: In password grant the resource owner(end user) prompts its credentials(usually username and password) to obtain the access token, this is a common login flow. However this grant type is high level grant, that must be only be controlled by trusted applications(oauth clients controlled by the resource server developers). You can notice that no facebook app allow you to request email and password as authorization, instead a dialog is used as intermediary, this dialog is in facebook's control.
-
client_credentials: This grant is used as administrator credential in the resource, to request this grant you must specify a client_id and a client_secret, this credential will grant to the oauth client access to resources that are exclusive property of the resource server(for example I can grant you access to the trending hashtags but can't grant you access to read @foobar's twits)
-
Access Token: Is a random text string generated or no by cryptography, it represents a credential granted by the authorization server to the client, this credential must be used in every protected resource request as a mechanism of identity. An access token represents a limited scope that can be accessed, how many time it will last and a refresh token to renew the credential when expired.
-
Scope: A scope is a unique string representing a resource's permission, for example in facebook the scope publish_actions represent a permission to publish actions so if you have an access token with publish_actions scope then you can only publish actions, an access token can have from one to many scopes depends on your implementation how many scopes are granted on a single access token request.
The scope terminology is now defined and it was mentioned before in the authorization flow, so you must know how the scope of your requests is computed:
A scope is a string representing an individual resource, in this case the scope is build from the endpoints using each portion of the url as a scope fragment
Examples:
GET /users/lists scope: users.lists.get POST /users/lists scope: users.lists.post GET /users/feeds/:id([0-9]+) scope: users.feeds.id.get GET /users/notifications/:feed_id([a-zA-Z0-9]+)/:timestamp scope: users.notifications.feed_id.timestamp.get
The formula is simple just replace any slash(/) with a dot(.), any regex pattern used in the express route definition will be trimmed and only the param name will be concatenated to the resulting string. Finally add the http verb to the end this will guarantee a safe scope engeenering.
Consider this formula to know how to store your scopes, remember, this server does not determine how the scopes are granted or stored, the server does not know wich scopes you have implemented.
Before making any test request you should know that there is an internal API wich supports clients, applications and accounts in the server, this API is convenientlly protected by the same server, so everytime you implment oauth-server2 this endpoints will be automatically bound to your express app
Field | Type | Required | Description |
---|---|---|---|
suscriber_id | Numeric | false | Oauth suscriber unique id, it is auto generated |
suscriber_name | String | true | Suscriber name, must be unique |
suscriber_email | String | true | Suscriber email |
suscriber_contact | String | false | Suscriber contact phone |
suscriber_logo | Image | false | Suscriber image |
suscriber_url | String | false | Suscriber web page or url |
Field | Type | Required | Description |
---|---|---|---|
client_id | String | false | Client id used to fetch tokens is auto an generated 256bytes crypto hash |
client_secret | String | false | Client secret used to fetch token is an auto generated 512bytes crypto hash |
suscriber_id | Numeric | true | Identifier of the suscriber |
redirect_uri | String | true | Url to be redirected(used by authorization_code grant) |
app_name | String | true | Name of the application to be registered |
app_logo | Numeric | false | Logo of the application to be registered |
grant_types | String | false | Grant types allowed for this client |
Field | Type | Required | Description |
---|---|---|---|
account_id | Numeric | false | Account unique identifier, is an auto generated integer |
client_id | String | true | Client id from(client_id) |
account_name | String | true | Name of the account(used for administrative purposses) |
account_details | JSON | false | Details of the account for your usage as key value stuff |
scopes | String | false | Base scopes to be granted for client_credentials |
Endpoint | Verb | Description |
---|---|---|
/oauth/oauth_suscribers | GET | Find all the oauth suscriber registered |
/oauth/oauth_suscribers | POST | Creates a new oauth suscriber |
/oauth/oauth_suscribers/:suscriber_id | GET | Find the oauth suscriber with the given id |
/oauth/oauth_suscribers/:suscriber_id | PUT | Updates the oauth suscriber with the given id |
/oauth/oauth_suscribers/:suscriber_id | DELETE | Deletes the oauth suscriber with the given id |
/oauth/oauth_clients | GET | Find all the oauth clients registered |
/oauth/oauth_clients | POST | Creates a new oauth client |
/oauth/oauth_clients/:client_id | GET | Find the oauth client with the given id |
/oauth/oauth_clients/:client_id | PUT | Updates the oauth client with the given id |
/oauth/oauth_clients/:client_id | DELETE | Deletes the oauth client with the given id |
/oauth/api_accounts | GET | Find all the api accounts registered |
/oauth/api_accounts | POST | Creates a new api account |
/oauth/api_accounts/:client_id | GET | Find the api account with the given id |
/oauth/api_accounts/:client_id | PUT | Updates the api account with the given id |
/oauth/api_accounts/:client_id | DELETE | Deletes the api account with the given id |
/oauth/token | POST | Create a new access token, see /oauth/token endpoint docs |
/oauth/token/:access_token | GET | Fetch the information about the current token |
/oauth/token/:access_token | DELETE | Revokes the access token |
In the above chart you saw the available endpoints on the internal API, however there is an specific endpoint to fetch access tokens wich has an unique way to do stuff, and all depends on the grant type but general rules are:
- Is required a post request with Content-Type application/x-www-form-urlencoded
- Is required to send: client_id, client_secret, and grant_type initially
- Depending on the grant type any additional parameter can be required
After a successful request you will get a json access token like:
{
"token_type": "Bearer",
"access_token": "1b4c05bd89ccc18d6a49e006bca5a52b7af403fbcd96aaa9ccb454a62303bc72",
"expires_in": 3600,
"refresh_token": "0fe535b59894e5bf5c8ac98b79a2feb855cad3764a494f0a4f9db00fc934f2af",
"token_data": {
"account_id": "1",
"clientId": "client_id_here",
"account_name": "SIGIS API Manager",
"account_details": {
"name": "Soluciones Integrales GIS SIGIS C.A",
"description": "default Tenant",
"phone": "+58(212) 954.11.93",
"email": "buzon@sigis.com.ve",
"uri": "http://www.sigis.com.ve/"
},
"scopes": "*",
"creation_stamp": "2016-10-24T10:56:02.338Z"
},
"scope": "*"
}
Where the following fields are of interest for you:
- access_token: The token itself that must be issued for your protected requests
- expires_in: Time in seconds after the token has been created to expire, in this case 3600 seconds(an hour)
- refresh_token: A token used to change your current access token for a new one(useful to change a token after it expired)
After some yadayada and theorical stuff is time to rock
npm install oauth-server2
#Configuration
oauth-server2 requires the following configurations:
Configuration | Description |
---|---|
db | Main sequelize's configurations this will determine the main database of the server |
memcached | Memcached configurations required to start memcached connection |
grantHandlers(optional) | List of custom grant types supported by the server(you will se an example later) |
In this example you will se an step by step installation example of oauth-server2
Before we begin I recommend you to create a config.js file with sequelize's ORM connection and memecached credentials
module.exports = {
db : {
database : "librerias_sigis",
username : "postgres",
password :"18911662",
logging: false,
host: "localhost",
dialect: "postgres"
},
memcached: {
servers : [ "127.0.0.1:11211" ],
options : {
keyCompression : true,
poolSize : 5,
reconnect : 3500,
retry: 3500
}
}
};
npm install --save oauth-server2
npm install --save express body-parser
"use strict"
const express = require("express");
const bodyParser = require("body-parser");
const api = express();
const config = require("./config");
const OAuthServer = require("oauth-server2");
OAuthServer.init(api,{
db : config.db,
memcached: config.memcached
});
To protect a router you must use oauth-server2 middleware obtained by the method authorizeResource, this will return an array of middleware used to protect your resource vía oauth.
let router = express.Router();
//Protecting the resource using the middleware
router.route("/private").get(OAuthServer.authorizeResource(),(req,res,next)=> {
res.send({
message : "access to the private stuff",
other: req.accessToken
});
});
//public resource
router.route("/public",(req,res,next)=> {
res.send({
message: "This is public junk"
})
});
api.use("/",router);
api.listen(9000,() => {
console.log("Listening on port 9000");
});
To test the server just make requests to the route you just added(/private)
curl -X GET -H "Cache-Control: no-cache" "http://localhost:9000/private"
The result you will get is:
{
"error": "invalid_request",
"error_description": "No ha especificado un access token"
}
If you request an access token on the endpoint /oauth/token
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -H "Cache-Control: no-cache" -d 'grant_type=client_credentials&client_id=client_id_here&client_secret=secret_small_secret' "http://localhost:9000/oauth/token"
Se le otorgara un access token con la información de su grant:
{
"token_type": "Bearer",
"access_token": "1b4c05bd89ccc18d6a49e006bca5a52b7af403fbcd96aaa9ccb454a62303bc72",
"expires_in": 3600,
"refresh_token": "0fe535b59894e5bf5c8ac98b79a2feb855cad3764a494f0a4f9db00fc934f2af",
"token_data": {
"account_id": "1",
"clientId": "client_id_here",
"account_name": "SIGIS API Manager",
"account_details": {
"name": "Soluciones Integrales GIS SIGIS C.A",
"description": "default Tenant",
"phone": "+58(212) 954.11.93",
"email": "buzon@sigis.com.ve",
"uri": "http://www.sigis.com.ve/"
},
"scopes": "*",
"creation_stamp": "2016-10-24T10:56:02.338Z",
"client": {
"appLogo": "/app_logo",
"clientId": "client_id_here",
"clientSecret": "secret",
"suscriberId": "1",
"redirectUrl": "http://webserver2a-local:5760",
"appName": "ApiConsole",
"grantTypes": "client_credentials,auth_code,password,refresh_token",
"creationStamp": "2016-10-24T10:56:02.338Z"
}
},
"scope": "*"
}
The access_token property found on the json response will be your authorization triumph card
curl -X GET -H "Authorization: Bearer 1b4c05bd89ccc18d6a49e006bca5a52b7af403fbcd96aaa9ccb454a62303bc72" -H "Cache-Control: no-cache" "http://webserver2a-local:9000/private"
This call will grant us access to the private stuff
{
"message": "access to the private stuff",
"other": {
"accessToken": "1b4c05bd89ccc18d6a49e006bca5a52b7af403fbcd96aaa9ccb454a62303bc72",
"clientId": "WSKFIWRUMWORWWBNKUBV648913546103199900321419350531",
"expires": "2016-10-24T16:47:55.460Z",
"scopes": "*",
"tokenData": {
"account_id": "1",
"clientId": "client_id",
"account_name": "SIGIS API Manager",
"account_details": {
"name": "Soluciones Integrales GIS SIGIS C.A",
"description": "default Tenant",
"phone": "+58(212) 954.11.93",
"email": "buzon@sigis.com.ve",
"uri": "http://www.sigis.com.ve/"
},
"scopes": "*",
"creation_stamp": "2016-10-24T10:56:02.338Z",
"client": {
"appLogo": "/app_logo",
"clientId": "client_id",
"clientSecret": "secret",
"suscriberId": "1",
"redirectUrl": "http://webserver2a-local:5760",
"appName": "ApiConsole",
"grantTypes": "client_credentials,auth_code,password,refresh_token",
"creationStamp": "2016-10-24T10:56:02.338Z"
}
},
"creation_stamp": "2016-10-24T11:06:32.120Z",
"is_active": true
}
}
As you could see is really easy to get started with oauth-server2 in just few steps, some of the tedious stuff of developing oauth servers is under the control of this server.
oauth-server2 allow you to create custom grant types, by default password grant is not implemented, howerver is really easy to add it following the example:
This module was developed using a TDD based approach with mocha, chai and superagent a basic suite is implemented with two basic aspects:
- internal api setup: It is checked that every internal api endpoint is usable
- authorization framework tests
npm test