Skip to content
This repository has been archived by the owner on Aug 28, 2023. It is now read-only.

Commit

Permalink
Merge pull request #277 from AzureAD/dev
Browse files Browse the repository at this point in the history
release 3.0.4
  • Loading branch information
lovemaths authored Jan 9, 2017
2 parents 84e23ff + 72e8510 commit 9121603
Show file tree
Hide file tree
Showing 36 changed files with 3,477 additions and 126 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
node_modules/
test/End_to_end_test/app/node_modules/
test/End_to_end_test/debug.log
.idea/
npm-debug.log
federationmetadata.xml
Expand Down
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ sudo: required
dist: trusty
language: node_js
node_js:
- "6"
- "5"
- "5.1"
- "4"
- "4.2"
- "4.1"
- "4.0"
before_install: npm install -g grunt-cli
install: npm install
install: npm install

40 changes: 39 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,42 @@
<a name="3.0.1"></a>
<a name="3.0.4"></a>
# 3.0.4

## OIDCStrategy

### New features

* added support for `prompt`, `login_hint` and `domain_hint` parameters

* added `tfp` claim support for B2C

* token validation clock skew is now configurable using `clockSkew` option

* added `thumbprint` and `privatePEMKey` options for client assertion support.

## BearerStrategy

* token validation clock skew is now configurable using `clockSkew` option

## Tests

* added end to end automated tests for OIDCStrategy and BearerStrategy

## Bug fixes

* [#231](https://github.com/AzureAD/passport-azure-ad/issues/231) Support client_asserton for OIDC auth flow

* [#245](https://github.com/AzureAD/passport-azure-ad/issues/245) Make clock skew configurable

* [#251](https://github.com/AzureAD/passport-azure-ad/issues/251) Multiple Audiences with Bearer Strategy

* [#254](https://github.com/AzureAD/passport-azure-ad/issues/254) passReqToCallback does not work with bearer strategy

* [#256](https://github.com/AzureAD/passport-azure-ad/issues/256) Support 'tfp' for B2C

* [#261](https://github.com/AzureAD/passport-azure-ad/issues/261) prompt,domain_hint and login_hint are missing in the query params sent to endpoint

* [#264](https://github.com/AzureAD/passport-azure-ad/issues/264) OIDC authentication fails when oauth token_type is 'bearer' and not 'Bearer'

# 3.0.3

## Bug fixes
Expand Down
14 changes: 12 additions & 2 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = function loadGrunt(grunt) {
test: {
options: {
reporter: 'spec',
clearRequireCache: true
},
src: ['test/Chai-passport_test/*_test.js'],
},
Expand Down Expand Up @@ -58,6 +59,15 @@ module.exports = function loadGrunt(grunt) {
grunt.registerTask('printMsg_chai-passport', () => {
grunt.log.writeln('\n\n\n======= Running tests in test/chai-passport_test =======\n\n\n');
});
grunt.registerTask('run_all_tests', ['printMsg_chai-passport', 'mochaTest', 'printMsg_nodeunit', 'nodeunit']);
grunt.registerTask('default', 'run_all_tests');
grunt.registerTask('printMsg_end_to_end_Test', () => {
grunt.log.writeln('\n\n\n======= Running end to end tests in test/End_to_end_test =======\n\n\n');
});
grunt.registerTask('end_to_end_test', () => {
grunt.config('mochaTest.test.src', 'test/End_to_end_test/*_test.js');
grunt.task.run(['mochaTest']);
});
grunt.registerTask('e2e', ['printMsg_end_to_end_Test', 'end_to_end_test']);
grunt.registerTask('run_tests_with_e2e', ['printMsg_chai-passport', 'mochaTest', 'printMsg_nodeunit', 'nodeunit', 'printMsg_end_to_end_Test', 'end_to_end_test']);
grunt.registerTask('run_tests', ['printMsg_chai-passport', 'mochaTest', 'printMsg_nodeunit', 'nodeunit']);
grunt.registerTask('default', 'run_tests');
};
97 changes: 93 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ and with [Microsoft Active Directory Federation Services](http://en.wikipedia.or
_passport-azure-ad_ has a known security vulnerability affecting versions <1.4.6 and 2.0.0. Please update to >=1.4.6 or >=2.0.1 immediately. For more details, see the [security notice](https://github.com/AzureAD/passport-azure-ad/blob/master/SECURITY-NOTICE.MD).

## 2. Versions
Current version - 3.0.3
Current version - 3.0.4
Minimum recommended version - 1.4.6
You can find the changes for each version in the [change log](https://github.com/AzureAD/passport-azure-ad/blob/master/CHANGELOG.md).

Expand Down Expand Up @@ -65,6 +65,7 @@ passport.use(new OIDCStrategy({
scope: config.creds.scope,
loggingLevel: config.creds.loggingLevel,
nonceLifetime: config.creds.nonceLifetime,
clockSkew: config.creds.clockSkew,
},
function(iss, sub, profile, accessToken, refreshToken, done) {
if (!profile.oid) {
Expand Down Expand Up @@ -139,9 +140,23 @@ passport.use(new OIDCStrategy({
Required to set to true if you want to use http url for redirectUrl like `http://localhost:3000`.

* `clientSecret` (Conditional)

When `responseType` is not `id_token`, we have to provide client credential to redeem the authorization code. This credential could be client secret or client assertion. Non-B2C tenant supports both flows, but B2C tenant only supports client secret flow.

Required if the `responseType` is not 'id_token'. This is the app key of your app in AAD. For B2C, the app key sometimes contains \, please replace \ with two \'s in the app key, otherwise \ will be treated as the beginning of an escaping character.

For B2C tenant: `clientSecret` is required if the `responseType` is not 'id_token'.

For non-B2C tenant: If `responseType` is not `id_token`, developer must provide either `clientSecret`, or `thumbprint` and `privatePEMKey`. We use `clientSecret` if it is provided; otherwise we use `thumbprint` and `privatePEMKey` for client assertion flow.

`clientSecret` is the app key of your app in AAD. For B2C, the app key sometimes contains \, please replace \ with two \'s in the app key, otherwise \ will be treated as the beginning of an escaping character.

* `thumbprint` (Conditional)

Required if you want to use client assertion flow. `thumbprint` is the base64url format of the thumbprint (hash value) of the public key.

* `privatePEMKey` (Conditional)

Required if you want to use client assertion flow. `privatePEMKey` is the private pem key string.

* `isB2C` (Conditional)

Required to set to true if you are using B2C tenant.
Expand All @@ -165,6 +180,10 @@ passport.use(new OIDCStrategy({
* `nonceLifetime` (Optional)

The lifetime of nonce in session in seconds. The default value is 3600 seconds.

* `clockSkew` (Optional)

This value is the clock skew (in seconds) allowed in token validation. It must be a positive integer. The default value is 300 seconds.

##### 5.1.1.3 Verify callback

Expand Down Expand Up @@ -238,6 +257,12 @@ the strategy.
* This option only applies to the login request, in other words, the request which is not supposed to contain code or id_token. Passport saves the `tenantIdOrName` value in session before sending the authentication request. When we receive a request containing code or id_token, we retrieve the saved `tenantIdOrName` value from session and use that value.
* If you are using B2C common endpoint, then `tenantIdOrName` must be used for every login request.

* `domain_hint`: if you want to specify the domain that the user should use to sign in. This option is not supported for B2C tenant.

* `login_hint`: if you want to prefill the username with a given value in the login page. The value should be the `upn` of an user, not the email (most times they are the same though).

* `prompt`: v1 and v2 endpoint support `login`, `consent` and `admin_conset`; B2C endpoint only supports `login`.

Example:

```
Expand Down Expand Up @@ -267,6 +292,7 @@ var options = {
allowMultiAudiencesInToken: config.creds.allowMultiAudiencesInToken,
audience: config.creds.audience,
loggingLevel: config.creds.loggingLevel,
clockSkew: config.creds.clockSkew,
};

var bearerStrategy = new BearerStrategy(options,
Expand Down Expand Up @@ -353,6 +379,10 @@ var bearerStrategy = new BearerStrategy(options,

Logging level. 'info', 'warn' or 'error'.

* `clockSkew` (Optional)

This value is the clock skew (in seconds) allowed in token validation. It must be a positive integer. The default value is 300 seconds.

##### 5.2.1.3 Verify callback

If you set `passReqToCallback` option to false, you can use the following verify callback
Expand Down Expand Up @@ -393,12 +423,71 @@ In the library root folder, type the following command to install the dependency
$ npm install
```

Then type the following command to run tests:
### 6.1. Run all tests except the end to end tests

Type the following command to run tests:

```
$ npm test
```

### 6.2. Run all tests including the end to end tests

#### 6.2.1. Create test applications

First you need to register one application in v1 tenant, one in v2 tenant and one in B2C tenant.

For the v2 application, you should register it at https://apps.dev.microsoft.com/ instead of Azure Portal.

For the B2C application, create four policies named 'B2C_1_signin', 'B2C_1_signup', 'B2C_1_updateprofile',
'B2C_1_resetpassword'. For each policy, select 'Local Account' as the identity provider, and select the
following:

* 'B2C_1_signup':

* Sign-up attributes: 'Display Name', 'Email Address', 'Given Name', 'Surname'

* Application claims: 'Display Name', Email Addresses', 'Given Name', 'Identity Provider', 'Surname', 'Users Object ID'

* 'B2C_1_updateprofile':

* Profile attributes: 'Display Name', 'Given Name', 'Surname'

* Application claims: 'Display Name', Email Addresses', 'Given Name', 'Identity Provider', 'Surname', 'Users Object ID'

* 'B2C_1_signin':

* Application claims: 'Display Name', Email Addresses', 'Given Name', 'Identity Provider', 'Surname', 'Users Object ID'

* 'B2C_1_signin_acr':

* Application claims: 'Display Name', Email Addresses', 'Given Name', 'Identity Provider', 'Surname', 'Users Object ID'

* After creating this policy, go the blade of this policy, click 'Edit' and then 'Token, session & SSO config'. Now switch the 'Claim representing policy ID' from 'tfp' to 'acr' and save the change.

* 'B2C_1_resetpassword':

* Application claims: 'Email Addresses', 'Given Name', 'Users Object ID'

You will also need to click the 'Run now' button in the 'B2C_1_signup' blade to create an user.

#### 6.2.2. Fill the test parameters

Open `test/End_to_end_test/script.js`, set `is_test_parameters_completed` parameter to true. For `test_parameters` variable, fill in the tenant id/client id/client secret of your applications, and the username/password of your application user. The 'oid' value is the object id of your application user. To find the 'oid' value, go to your tenant, click 'Users and groups', find your user and click it. The Object ID value will show up in the new blade.

For `thumbprint` and `privatePEMKey` parameters, you need to specify a certificate for your app and register the public key in Azure Active Directory. `thumbprint` is the base64url format of the thumbprint of the public key, and `privatePEMKey` is the private pem key string. For a v1 tenant, you can follow [this post](http://www.andrewconnell.com/blog/user-app-app-only-permissions-client-credentials-grant-flow-in-azure-ad-office-365-apis) to generate a certificate and register the public key. For a v2 tenant, you can go to your application page in the [v2 portal](https://apps.dev.microsoft.com) and click `Generate New Key Pair`. A certificate will be generated for you to download. The corresponding public key is automatically registered in this case.

#### 6.2.3. Run the tests

Type the following commands to run the tests:

```
$ cd test/End_to_end_test/app
$ npm install
$ npm install grunt -g
$ grunt run_tests_with_e2e
```

Tests will run automatically and in the terminal you can see how many tests are passing/failing. More details can be found [here](https://github.com/AzureAD/passport-azure-ad/blob/master/contributing.md).

## 7. Samples and Documentation
Expand Down
17 changes: 4 additions & 13 deletions contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,14 @@ For the testing tools, we use both nodeunit and [chai-passport-strategy](https:/

All the test files should have a _test suffix in their names and be placed in the correct subdirectory, depending on the testing tools used. The following is the rule:

* **nodeunit_test**: contains all nodeunit tests
* **chai-passport_test**: contains all chai-passport-strategy tests
* **Nodeunit_test**: contains all nodeunit tests
* **Chai-passport_test**: contains all chai-passport-strategy tests
* **End_to_end_test**: contains all end to end tests using Selenium with chrome webdriver
* **resource**: contains all shared resources for testing (for example, pem key file)

### How to run tests on your machine

In the library root folder, type the following command to install the dependency packages:

```
$ npm install
```
Then type the following command to run tests:

```
$ npm test
```
Tests will run automatically and in the terminal you can see how many tests are passing/failing. Ensure all tests are passing before submitting your pull request.
Please refer to section 6 in README.md for the test instructions.

### Automatic Travis test

Expand Down
37 changes: 26 additions & 11 deletions lib/bearerstrategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const passport = require('passport');
const util = require('util');

const aadutils = require('./aadutils');
const CONSTANTS = require('./constants');
const jwt = require('./jsonWebToken');
const Metadata = require('./metadata').Metadata;
const Log = require('./logging').getLogger;
Expand Down Expand Up @@ -132,6 +133,10 @@ const B2C_PREFIX = 'b2c_1_';
* (3) Description:
* We invalidate the `aud` claim in access_token against `audience`. The default value is `clientID`
*
* - `clockSkew` (1) Optional
* (2) must be a positive integer
* (3) Description:
* the clock skew (in seconds) allowed in token validation, default value is CLOCK_SKEW
* Examples:
*
* passport.use(new BearerStrategy(
Expand Down Expand Up @@ -181,6 +186,12 @@ function Strategy(options, verifyFn) {
// Set up the default values
//---------------------------------------------------------------------------

// clock skew. Must be a postive integer
if (options.clockSkew && (typeof options.clockSkew !== 'number' || options.clockSkew <= 0 || options.clockSkew % 1 !== 0))
throw new Error('clockSkew must be a positive integer');
if (!options.clockSkew)
options.clockSkew = CONSTANTS.CLOCK_SKEW;

// default value of passReqToCallback is false
if (options.passReqToCallback !== true)
options.passReqToCallback = false;
Expand All @@ -193,9 +204,12 @@ function Strategy(options, verifyFn) {
if (options.allowMultiAudiencesInToken !== true)
options.allowMultiAudiencesInToken = false;

// default value of audience is clientID
if (!options.audience || options.audience === '')
options.audience = options.clientID;
// if options.audience is a string or an array of string, then we use it;
// otherwise we use the clientID
if (options.audience && typeof options.audience === 'string')
options.audience = [options.audience];
else if (!options.audience || !Array.isArray(options.audience) || options.length === 0)
options.audience = [options.clientID, 'spn:' + options.clientID];

// default value of isB2C is false
if (options.isB2C !== true)
Expand Down Expand Up @@ -293,15 +307,15 @@ Strategy.prototype.jwtVerify = function jwtVerifyFunc(req, token, metadata, opti

log.info('In Strategy.prototype.jwtVerify: VerifiedToken: ', verifiedToken);

if (self.passReqToCallback) {
if (self._options.passReqToCallback) {
log.info('In Strategy.prototype.jwtVerify: We did pass Req back to Callback');
return self._verify(req, verifiedToken, done);
} else {
log.info('In Strategy.prototype.jwtVerify: We did not pass Req back to Callback');
return self._verify(verifiedToken, done);
}
});
}
};

/*
* We let the metadata loading happen in `authenticate` function, and use waterfall
Expand Down Expand Up @@ -346,12 +360,10 @@ Strategy.prototype.authenticate = function authenticateStrategy(req) {
.concat(`?${aadutils.getLibraryProductParameterName()}=${aadutils.getLibraryProduct()}`)
.concat(`&${aadutils.getLibraryVersionParameterName()}=${aadutils.getLibraryVersion()}`); ;

if (self._options.isB2C) {
params.cacheKey = 'policy: ' + self._options.policyName;
if (self._options.isB2C)
params.metadataURL = params.metadataURL.concat(`&p=${self._options.policyName}`)
} else {
params.cacheKey = 'ordinary';
}

params.cacheKey = params.metadataURL;

log.info(`In Strategy.prototype.authenticate: ${JSON.stringify(params)}`);

Expand Down Expand Up @@ -383,6 +395,9 @@ Strategy.prototype.authenticate = function authenticateStrategy(req) {
optionsToValidate.allowMultiAudiencesInToken = self._options.allowMultiAudiencesInToken;
optionsToValidate.ignoreExpiration = self._options.ignoreExpiration;

// clock skew
optionsToValidate.clockSkew = self._options.clockSkew;

log.info(`In Strategy.prototype.authenticate: we will validate the following options: ${optionsToValidate}`);

return next();
Expand Down Expand Up @@ -463,7 +478,7 @@ Strategy.prototype.loadMetadata = function(params, next) {
return memoryCache.wrap(params.cacheKey, (cacheCallback) => {
metadata.fetch((fetchMetadataError) => {
if (fetchMetadataError) {
return self.failWithLog(`In Strategy.prototype.authenticate: Unable to fetch metadata: ${fetchMetadataError}`);
return self.failWithLog('In loadMetadata: Unable to fetch metadata');
}
return cacheCallback(null, metadata);
});
Expand Down
3 changes: 3 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ CONSTANTS.TENANTID_REGEX = /^[0-9a-zA-Z-]+$/;

CONSTANTS.CLOCK_SKEW = 300; // 5 minutes

CONSTANTS.CLIENT_ASSERTION_JWT_LIFETIME = 600; // 10 minutes
CONSTANTS.CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';

module.exports = CONSTANTS;
4 changes: 2 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@
/**
* Export BearerStrategy and OIDCStrategy.
*/
exports.BearerStrategy = require('./bearerstrategy');
exports.OIDCStrategy = require('./oidcstrategy');
module.exports.BearerStrategy = require('./bearerstrategy');
module.exports.OIDCStrategy = require('./oidcstrategy');
Loading

0 comments on commit 9121603

Please sign in to comment.