GithubHelp home page GithubHelp logo

restify-oauth2's Introduction

OAuth 2 Endpoints for Restify

This package provides a very simple OAuth 2.0 endpoint for the Restify framework. In particular, it implements the Client Credentials and Resource Owner Password Credentials flows only.

What You Get

If you provide Restify–OAuth2 with the appropriate hooks, it will:

  • Set up a token endpoint, which returns access token responses or correctly-formatted error responses.
  • For all other resources, when an access token is sent via the Authorization header, it will validate it:
  • If no access token is sent, it ensures that req.username is set to null; furthermore, none of your hooks are called, so you can be sure that no properties that they set are present.
    • You can then check for these conditions whenever there is a resource you want to protect.
    • If the user tries to access a protected resource, you can use Restify–OAuth2's res.sendUnauthenticated() to send appropriate 401 errors with helpful WWW-Authenticate and Link headers, or its res.sendUnauthorized() to send appropriate 403 errors with similar headers.

Use and Configuration

To use Restify–OAuth2, you'll need to pass it your server plus some options, including the hooks discussed below. Restify–OAuth2 also depends on the built-in authorizationParser and bodyParser plugins, the latter with mapParams set to false. So in short, it looks like this:

var restify = require("restify");
var restifyOAuth2 = require("restify-oauth2");

var server = restify.createServer({ name: "My cool server", version: "1.0.0" });
server.use(restify.authorizationParser());
server.use(restify.bodyParser({ mapParams: false }));

restifyOAuth2.cc(server, options);
// or
restifyOAuth2.ropc(server, options);

Unfortunately, Restify–OAuth2 can't be a simple Restify plugin. It needs to install a route for the token endpoint, whereas plugins simply run on every request and don't modify the server's routing table.

Options

The options you pass to Restify–OAuth2 depend heavily on which of the two flows you are choosing. There are some options common to both flows, but the options.hooks hash will vary depending on the flow. Once you provide the appropriate hooks, you get an OAuth 2 implementation for free.

Client Credentials Hooks

The idea behind this very simple OAuth 2 flow is that your API clients identify themselves with client IDs and secrets, and if those values authenticate, you grant them an access token they can use for further requests. The advantage of this over simply requiring basic access authentication headers on every request is that now you can set those tokens to expire, or revoke them if they fall in to the wrong hands.

To install Restify–OAuth2's client credentials flow into your infrastructure, you will need to provide it with the following hooks in the options.hooks hash. You can see some example CC hooks in the demo application.

grantClientToken({ clientId, clientSecret }, req, cb)

Checks that the API client is authorized to use your API, and has the correct secret. It should call back with a new token for that client if so, or false if the credentials are incorrect. It can also call back with an error if there was some internal server error while validating the credentials.

authenticateToken(token, req, cb)

Checks that a token is valid, i.e. that it was granted in the past by grantClientToken. It should call back with true if so, or false if the token is invalid. It can also call back with an error if there was some internal server error while looking up the token. If the token is valid, it is likely useful to set a property on the request object indicating that so that your routes can check it later, e.g. req.authenticated = true or req.clientId = lookupClientIdFrom(token).

Resource Owner Password Credentials Hooks

The idea behind this OAuth 2 flow is that your API clients will prompt the user for their username and password, and send those to your API in exchange for an access token. This has some advantages over simply sending the user's credentials to the server directly. For example, it obviates the need for the client to store the credentials, and allows expiration and revocation of tokens. However, it does imply that you trust your API clients, since they will have at least one-time access to the user's credentials.

To install Restify–OAuth2's resource owner password credentials flow into your infrastructure, you will need to provide it with the following hooks in the options.hooks hash. You can see some example ROPC hooks in the demo application.

validateClient({ clientId, clientSecret }, req, cb)

Checks that the API client is authorized to use your API, and has the correct secret. It should call back with true or false depending on the result of the check. It can also call back with an error if there was some internal server error while doing the check.

grantUserToken({ clientId, clientSecret, username, password }, req, cb)

Checks that the API client is authenticating on behalf of a real user with correct credentials. It should call back with a new token for that user if so, or false if the credentials are incorrect. It can also call back with an error if there was some internal server error while validating the credentials.

authenticateToken(token, req, cb)

Checks that a token is valid, i.e. that it was granted in the past by grantUserToken. It should call back with true if so, or false if the token is invalid. It can also call back with an error if there was some internal server error while looking up the token. If the token is valid, it is likely useful to set a property on the request object indicating that so that your routes can check it later, e.g. req.authenticated = true or req.username = lookupUsernameFrom(token).

Scope-Granting Hook

Optionally, it is possible to limit the scope of the issued tokens, so that you can implement an authorization system in your application in addition to simple authentication.

grantScopes(credentials, scopesRequested, req, cb)

This hook is called after the token has been granted by authenticateToken. In the client credentials flow, credentials will be { clientId, clientSecret, token }; in the resource owner password credentials flow, it will be { clientId, clientSecret, username, password, token }. In both cases, scopesRequested will be an array of the requested scopes.

This hook can respond in several ways:

  • It can call back with true to grant all of the requested scopes.
  • It can call back with false to indicate that the requested scopes are invalid, unknown, or exceed the set of scopes that should be granted to the given credentials.
  • It can call back with an array to grant a different set of scopes.
  • It can call back with an error if there was some internal server error while granting scopes.

In the cases of false or an internal server error, you should probably revoke the token before calling back, as the server will send the user an error response, instead of a successful token grant.

Other Options

The hooks hash is the only required option, but the following are also available for tweaking:

  • tokenEndpoint: the location at which the token endpoint should be created. Defaults to "/token".
  • wwwAuthenticateRealm: the value of the "Realm" challenge in the WWW-Authenticate header. Defaults to "Who goes there?".
  • tokenExpirationTime: the value returned for the expires_in component of the response from the token endpoint. Note that this is only the value reported; you are responsible for keeping track of token expiration yourself and calling back with false from authenticateToken when the token expires. Defaults to Infinity.

What Does That Look Like?

OK, let's try something a bit more concrete. If you check out the example servers used in the integration tests, you'll see our setup. Here we'll walk you through the more complicated resource owner password credentials example, but the idea for the client credentials example is very similar.

/

The initial resource, at which people enter the server.

  • If a valid token is supplied in the Authorization header, req.username is truthy, and the app responds with links to /public and /secret.
  • If no token is supplied, the app responds with links to /token and /public.
  • If an invalid token is supplied, Restify–OAuth2 intercepts the request before it gets to the application, and sends an appropriate 400 or 401 error.

/token

The token endpoint, managed entirely by Restify–OAuth2. It generates tokens for a given client ID/client secret/username/password combination.

The client validation and token-generation logic is provided by the application, but none of the ceremony necessary for OAuth 2 conformance, error handling, etc. is present in the application code: Restify–OAuth2 takes care of all of that.

/public

A public resource anyone can access.

  • If a valid token is supplied in the Authorization header, req.username contains the username, and the app uses that to send a personalized response.
  • If no token is supplied, req.username is null. The app still sends a response, just without personalizing.
  • If an invalid token is supplied, Restify–OAuth2 intercepts the request before it gets to the application, and sends an appropriate 400 or 401 error.

/secret

A secret resource that only authenticated users can access.

  • If a valid token is supplied in the Authorization header, req.username is truthy, and the app sends the secret data.
  • If no token is supplied, req.username is null, so the application uses res.sendUnauthenticated() to send a nice 401 error with WWW-Authenticate and Link headers.
  • If an invalid token is supplied, Restify–OAuth2 intercepts the request before it gets to the application, and sends an appropriate 400 or 401 error.

restify-oauth2's People

Contributors

alexdobeck avatar domenic avatar gmaniac avatar sybeck2k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

restify-oauth2's Issues

why mapParams must be false?

First of all, congratulations for the great work on the plugin!
I'm curious as to why mapParams must be false. It is a really useful feature, but can't be used with the plugin.

/token from javascript app opens browser basic auth popup

From either an AngularJS app, or Postman (A Chrome Rest Client), when I send a POST request to /token, and the authentication fails, the browser (Chrome) pops open the basic authentication dialog.

Note: When authentication is successful, there is no popup. It's only when authentication fails.

From what I've read, it's caused by the 401 response.

Is there a way around this so authentication failure can be handled via javascript in the browser?

I'm noting my POST request in case it has to do with the issue.

POST /token HTTP/1.1
Host: localhost:3000
Content-Type: application/json
Authorization: Basic Zm9vOmJhcg==
Cache-Control: no-cache

{"grant_type":"client_credentials"}

Testing - Advanced Rest Client in chrome

Hi,

I am trying to get the cc example working using the Advance rest client but cannot work it out.

Can you help..or provide a better example with more detail... e.g curl commands etc

Modify the token endpoint response body before sending

I need to modify the response for the token endpoint. Is there any way to insert some middleware that runs after the token grant middleware?

Restify does not allow stacking middlewares for a specific route using multiple sequential calls to server.VERB. If that was the case, I could stack another middleware function onto the token grant route by adding a route handler after calling the restify-oauth2 hook:

require('restify-oauth2').ropc(options)
server.post("/token", function(req, res, next) {})

So is there a recommended way to accomplish this?

Accessing request object in grantUserToken

Hello!

I have stumbled upon a scenario where I need to get some information (ip and user agent) from the request object in the grantUserToken hook. However, it doesn't seem to be a way to do that at the moment?

I solved it by passing with req as an argument to options.hooks.grantUserToken and matching that change in my hook, but I really don't want to change the source code of the package if I don't have to.

Is there any other way I could achieve this?

Post endpoints always check for token (ROPC)

The framework will always check for a token on POST requests. Here is an example:

app.post('/user/create', userCreate);
app.get('/user/create', userCreate);

function userCreate(req, res) {
     res.json({json:'Jay SON'});
     return;
}

The get requests returns correctly, the post request returns the following:

{
  "code": "BadRequestError",
  "message": "Bearer token required. Follow the oauth2-token link to get one!"
}

use both Authorization Grant CC and ROPC with same tokenEndpoint

Hi,

I would like to use both Authorization Grant CC and ROPC with same tokenEndpoint.

restifyOAuth2.cc(server, {tokenEndpoint: "/token", hooks: restifyOAuth2Hooks});

restifyOAuth2.ropc(server, {tokenEndpoint: "/token", hooks: restifyOAuth2RopcHooks});

What is the best way to setup or patch?

Regards,

Prevent WWW-Authenticate header

Hi,

I'm not sure I'm approaching this right, but when using restify with restify-oauth2 to secure an api endpoint for Web app - I'm getting a login dialog (at least on Chrome). To battle that I forked and added a new option noWwwAuthenticate next to the tokenEndpoint, wwwAuthenticateRealm, etc. and then I have a logic to not set appropriate header in makeErrorSenders.js (for both setWwwAuthenticateHeader and setWwwAuthenticateHeaderWithoutErrorInfo). I think I overlooked something in the docs or/and usage specs. Do reckon there is a proper approach to it?

In case you find this useful - I can create a pull request.

Add support for i18n strings

All the default messages are written in English, it would be awesome to be able to translate them to other languages.

Accessing options.tokenExpirationTime Inside hooks.grantClientToken()

This is a low priority issue/question.

I see that I can pass in a value to the option tokenExpirationTime. However, I don't see how I can access that value within the grantClientToken function (passed in via hooks). I'd like to have that tokenExpirationTime value so that my generateToken function can do a calculation on it.

As I see things now, I have to hardcode that value in grantClientToken and authenticateToken. (NOTE: I'm using 'cc'.)

Can you advise?

BTW, thanks for writing this module. I enjoy working with it!

Confusion about Implementing with passport-facebook

I am trying to grok the placement of this module in my application, because it appears to be the piece I am missing in my Restify/passport/passport-facebook app. With passport-facebook, I have the ability to have the user log in to Facebook, and when the user object is returned I can seek a match in my database and say "Oh hey yes, this user has a profile" or "no, create a profile" - but after that initial callback, I no longer have any authorization methods for my RESTful API.

Would I use restify-oauth2 in combination with passport and passport-facebook in order to achieve this, or do I just need to write a similar token passing mechanism that passport is completely missing?

Thanks!

node-oauth

I am trying to use node-oauth to do cc authentication against restify-oauth2

exports.authAppTokenHttp = testCase({
"Testing OAuth access over http" : function(test) {
       var oa = new OAuth2(
               '84545a68026413f860309e41e2d95e8cf17bf7bb52fadecdae0ad2189480e946',
               '054bddf87bb67685919410aec77d36166f0d84a7afc23152a1c9d0620cc64b8c',
               'http://localhost:19001/',
               null,
               'oauth2/app_request_token',
               null);
       
       oa.getOAuthAccessToken('', {'grant_type': 'client_credentials'}, function(error, access_token, refresh_token, result) {
           
           if (error) {
               console.log(error);
           }
           
           console.log('bearer: ', access_token);
           
           test.done();
       });
   }
});

I am getting:

{ statusCode: 400,
  data: '{"error":"invalid_request","error_description":"Must include a basic access authentication header."}' }

Are these two compatible?

CORS/OPTIONS response at /token requires auth

I'm building a one-page react app and am using a REST API for everything. The REST API is being called from react, and is on a different domain. The /token endpoint requires auth or it fails the OPTIONS request. Is this necessary? Can't options return proper CORS without requiring auth (firefox won't pass along basic auth on a preflight) headers or can there be an option to force preflight CORS OPTIONS response on the /token endpoint? I don't think the OPTIONS should require auth.

Token type is case sensitive.

Hi there,

Would you consider making the token type case insensitive as suggested by RFC6749? For instance, the Go implementation has already adopted case insensitivity.

I'm happy to make the change if you want me to. Thanks!

Restify 3

Can you test if this works with restify 3.x and update peerDependencies?

Cannot read property 'scheme' of undefined

ServerHandler:log TypeError: Cannot read property 'scheme' of undefined
at Server.ccOAuth2Plugin (/home/mike_api/node_modules/restify-oauth2/lib/common/makeSetup.js:55:41)
at next (/home/mike_api/node_modules/restify/lib/server.js:745:30)
at f (/home/mike_api/node_modules/restify/node_modules/once/once.js:17:25)
at Server.bunyan (/home/mike_api/node_modules/restify/lib/plugins/bunyan.js:36:9)
at next (/home/mike_api/node_modules/restify/lib/server.js:745:30)
at f (/home/mike_api/node_modules/restify/node_modules/once/once.js:17:25)
at Server.restifyResponseHeaders (/home/mike_api/node_modules/restify/lib/plugins/full_response.js:97:17)
at next (/home/mike_api/node_modules/restify/lib/server.js:745:30)
at f (/home/mike_api/node_modules/restify/node_modules/once/once.js:17:25)
at Server.restifyCORSSimple (/home/mike_api/node_modules/restify/lib/plugins/cors.js:95:13) +0ms

Prevent automatic addition of token endpoint

Is there any interest in supporting some way of disabling the automatic addition of the /token route in order to support usage of restify-oauth2 in multiple servers on the same domain?

My use case is that I'd like to have a restify server running at mydomain.com/api/auth which has a /token route, and then multiple other servers running at other points which do not grant tokens, but use the restify-oauth2 library to validate tokens.

To be more explicit, I'm interested in something like this:

// ====== server running at mydomain.com/api/auth ======

var restify = require('restify'),
   restifyOauth2 = require('restify-oauth2');

// server setup ...

restifyOAuth2.cc(server, {includeTokenEndpoint: true});
server.listen(8080);

// ====== server running at mydomain.com/api/users ======

var restify = require('restify'),
   restifyOauth2 = require('restify-oauth2');

// server setup...

restifyOAuth2.cc(server, {includeTokenEndpoint: false});

server.get('/', function (req, res) {
   if (!req.clientId) {
      return res.sendUnauthenticated();
   }

   res.contentType = 'application/json';
   res.send({message: 'I can tell you got a token from the other server'});
});

server.listen(8090);

If there's interest, I'm happy to work on a pull request. Thanks!

consumer key/secret question

Great module! This is a question rather than an issue.

I am building an API where we want to give the API user a key and secret. Then they use that key and secret to authenticate with our server and then can use the API however they like. They are not required to ask a real user for their username and password.

I would like to know if they supply the key for the clientId and username and the secret for the clientSecret and password like below, is that acceptable for my purpose?

validateClient(API_KEY, API_SECRET, cb)
grantToken(API_KEY, API_SECRET, cb) 

In other words, can I abuse the password credentials flow or should I be using a completely different flow for this use case?

Sending the initial resource your generated token

This is more of a question than an issue. I was able to get a token generated by POSTing /token with basic auth and grant_type = client_credentials, however, it is not clear to me how I can send the generated token to the initial resource ( / ). I always seem to get /public back in the response object. I tried sending access_token and token_type=Bearer in the headers and in the GET parameters without luck. How do you send the token and is there anything else required? Thanks for the great code!

cc implementation

Hi DD,

Great work on restify-oauth2, well written code. I'm working on a stack with Telerivet (SMS gateway) and restify + restify-oauth2. I'm running into an issue where the POST request (from Telerivet's webhook) doesn't have Basic xyz in the header. The clientid (actually a mobile phone number) and secret are in the body. Btw, I don't have control on the client request, it's 3rd party. I can think of these following options:

  1. Hack the req object, sets the username & password property, in the server.pre
  2. Implement some logic in the '/public' path (either issue the token directly or construct another request that conforms a standard post with Basic xyz header)
  3. Simply change the restify-oauth2 lib source

Any ideas or suggestions would be greatly appreciated. Thanks. MLP

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.