Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ application.

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.
error while doing the check. This hook is not required if `clientType: "public"` is passed as an option.

#### `grantUserToken(username, password, cb)`

Expand All @@ -111,6 +111,9 @@ The `hooks` hash is the only required option, but the following are also availab
* `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`.
* `clientType`: valid values are `"confidential"` (default) or `"public"`. Client credentials are not authenticated
for public clients. This option only applies to the [Resource Owner Password Credentials][ropc] flow. OAuth2 only
allows for confidential clients for the [Client Credentials][cc] flow.

## What Does That Look Like?

Expand Down
2 changes: 1 addition & 1 deletion lib/cc/grantToken.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ var validateGrantTokenRequest = require("../common/validateGrantTokenRequest");
var makeOAuthError = require("../common/makeOAuthError");

module.exports = function grantToken(req, res, next, options) {
if (!validateGrantTokenRequest("client_credentials", req, next)) {
if (!validateGrantTokenRequest("client_credentials", "confidential", req, next)) {
return;
}

Expand Down
4 changes: 3 additions & 1 deletion lib/cc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ var grantToken = require("./grantToken");

var grantTypes = "client_credentials";
var reqPropertyName = "clientId";
var requiredHooks = ["grantClientToken", "authenticateToken"];
var requiredHooks = function (options) {
return ["grantClientToken", "authenticateToken"];
}

module.exports = makeSetup(grantTypes, reqPropertyName, requiredHooks, grantToken);
19 changes: 12 additions & 7 deletions lib/common/makeSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,21 @@ module.exports = function makeSetup(grantTypes, reqPropertyName, requiredHooks,
if (typeof options.hooks !== "object" || options.hooks === null) {
throw new Error("Must supply hooks.");
}
requiredHooks.forEach(function (hookName) {
if (typeof options.hooks[hookName] !== "function") {
throw new Error("Must supply " + hookName + " hook.");
}
});


options = _.defaults(options, {
tokenEndpoint: "/token",
wwwAuthenticateRealm: "Who goes there?",
tokenExpirationTime: Infinity
tokenExpirationTime: Infinity,
clientType: "confidential"
});

requiredHooks(options).forEach(function (hookName) {
if (options.clientType === "public" && hookName === "validateClient") {
return;
}
if (typeof options.hooks[hookName] !== "function") {
throw new Error("Must supply " + hookName + " hook.");
}
});

// Allow `tokenExpirationTime: Infinity` (like above), but translate it into `undefined` so that
Expand Down
4 changes: 2 additions & 2 deletions lib/common/validateGrantTokenRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
var _ = require("underscore");
var makeOAuthError = require("./makeOAuthError");

module.exports = function validateGrantTokenRequest(grantType, req, next) {
module.exports = function validateGrantTokenRequest(grantType, clientType, req, next) {
function sendBadRequestError(type, description) {
next(makeOAuthError("BadRequest", type, description));
}
Expand All @@ -24,7 +24,7 @@ module.exports = function validateGrantTokenRequest(grantType, req, next) {
return false;
}

if (!req.authorization || !req.authorization.basic) {
if (clientType === "confidential" && (!req.authorization || !req.authorization.basic)) {
sendBadRequestError("invalid_request", "Must include a basic access authentication header.");
return false;
}
Expand Down
41 changes: 24 additions & 17 deletions lib/ropc/grantToken.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = function grantToken(req, res, next, options) {
}


if (!validateGrantTokenRequest("password", req, next)) {
if (!validateGrantTokenRequest("password", options.clientType, req, next)) {
return;
}

Expand All @@ -27,33 +27,40 @@ module.exports = function grantToken(req, res, next, options) {
return next(makeOAuthError("BadRequest", "invalid_request", "Must specify password field."));
}

var clientId = req.authorization.basic.username;
var clientSecret = req.authorization.basic.password;

options.hooks.validateClient(clientId, clientSecret, function (error, result) {
function grantUserTokenCallback(error, token) {
if (error) {
return next(error);
}

if (!result) {
return sendUnauthorizedError("invalid_client", "Client ID and secret did not validate.");
if (!token) {
return sendUnauthorizedError("invalid_grant", "Username and password did not authenticate.");
}

options.hooks.grantUserToken(username, password, function (error, token) {
res.send({
access_token: token,
token_type: "Bearer",
expires_in: options.tokenExpirationTime
});
next();
}

if (options.clientType === "confidential") {
var clientId = req.authorization.basic.username;
var clientSecret = req.authorization.basic.password;

options.hooks.validateClient(clientId, clientSecret, function (error, result) {
if (error) {
return next(error);
}

if (!token) {
return sendUnauthorizedError("invalid_grant", "Username and password did not authenticate.");
if (!result) {
return sendUnauthorizedError("invalid_client", "Client ID and secret did not validate.");
}

res.send({
access_token: token,
token_type: "Bearer",
expires_in: options.tokenExpirationTime
});
next();
options.hooks.grantUserToken(username, password, grantUserTokenCallback);
});
});
}
else {
options.hooks.grantUserToken(username, password, grantUserTokenCallback);
}
};
7 changes: 6 additions & 1 deletion lib/ropc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ var grantToken = require("./grantToken");

var grantTypes = "password";
var reqPropertyName = "username";
var requiredHooks = ["validateClient", "grantUserToken", "authenticateToken"];
var requiredHooks = function (options) {
if (options.clientType === "public") {
return ["grantUserToken", "authenticateToken"];
}
return ["validateClient", "grantUserToken", "authenticateToken"];
}

module.exports = makeSetup(grantTypes, reqPropertyName, requiredHooks, grantToken);