Skip to content

Commit

Permalink
interactive login in the node runtime (ms-rest-azure) (Azure#1066)
Browse files Browse the repository at this point in the history
* inital commit

* minor update

* some more updates
  • Loading branch information
amarzavery committed May 21, 2016
1 parent d25043a commit 3cb1bec
Show file tree
Hide file tree
Showing 14 changed files with 370 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Microsoft.Rest.Generator.Azure.NodeJS
{
public class AzureNodeJSCodeGenerator : NodeJSCodeGenerator
{
private const string ClientRuntimePackage = "ms-rest-azure version 1.13.1";
private const string ClientRuntimePackage = "ms-rest-azure version 1.14.0";

// List of models with paging extensions.
private IList<PageTemplateModel> pageModels;
Expand Down
2 changes: 1 addition & 1 deletion AutoRest/Generators/NodeJS/NodeJS/NodeJSCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace Microsoft.Rest.Generator.NodeJS
{
public class NodeJSCodeGenerator : CodeGenerator
{
private const string ClientRuntimePackage = "ms-rest version 1.13.1";
private const string ClientRuntimePackage = "ms-rest version 1.14.0";

public NodeJsCodeNamer Namer { get; private set; }

Expand Down
36 changes: 31 additions & 5 deletions ClientRuntimes/NodeJS/ms-rest-azure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Infrastructure for error handling, tracing, and http client pipeline configuration. Required by nodeJS Azure client libraries, generated using AutoRest.

- **Node.js version: 0.10.0 or higher**
- **Node.js version: 4.x.x or higher**


## How to Install
Expand All @@ -17,14 +17,40 @@ var msrestAzure = require('ms-rest-azure');
```
## Authentication

#### Interactive Login is the simplest and the best way to authenticate.
It provides a url and code that needs to be copied and pasted in a browser and authenticated over there. If successful,
the user will get a DeviceTokenCredentials object.
```javascript
var someAzureServiceClient = require('azure-arm-someService');
msRestAzure.interactiveLogin(function(err, credentials) {
var client = new someAzureServiceClient(credentials, 'your-subscriptionId');
client.someOperationGroup.method(param1, param2, function(err, result) {
if (err) console.log(err);
console.log(result);
});
});
```

#### Login with username and password
This mechanism will only work for organizational ids and ids that are not 2FA enabled.
Otherwise it is better to use the above mechanism (interactive login).
```javascript
var someAzureServiceClient = require('azure-arm-someService');
msRestAzure.loginWithUsernamePassword(username, password, function(err, credentials) {
var client = new someAzureServiceClient(credentials, 'your-subscriptionId');
client.someOperationGroup.method(param1, param2, function(err, result) {
if (err) console.log(err);
console.log(result);
});
});
```

### ServicePrincipal authentication
```javascript
//user authentication
var credentials = new msRestAzure.UserTokenCredentials('your-client-id', 'your-domain', 'your-username', 'your-password', 'your-redirect-uri');
//service principal authentication
var credentials = new msRestAzure.ApplicationTokenCredentials('your-client-id', 'your-domain', 'your-secret');
```
### Non-Interactive Authentication
If you need to create an automation account for non interactive or scripting scenarios then please take a look at the documentation over [here](https://github.com/Azure/azure-sdk-for-node/blob/autorest/Documentation/Authentication.md).
If you need to create an automation account for non interactive or scripting scenarios then please take a look at the documentation over [here](https://github.com/Azure/azure-sdk-for-node/blob/master/Documentation/Authentication.md).

## Related Projects

Expand Down
8 changes: 7 additions & 1 deletion ClientRuntimes/NodeJS/ms-rest-azure/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ var Constants = {
Succeeded: 'Succeeded',
Failed: 'Failed',
Canceled: 'Canceled'
}
},

DEFAULT_ADAL_CLIENT_ID: '04b07795-8ddb-461a-bbee-02f9e1bf7b46',

AAD_COMMON_TENANT: 'common',

DEFAULT_LANGUAGE: 'en-us'
};

exports = module.exports = Constants;
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var AzureEnvironment = require('../azureEnvironment');
* @param {object} [options] Object representing optional parameters.
* @param {AzureEnvironment} [options.environment] The azure environment to authenticate with.
* @param {string} [options.authorizationScheme] The authorization scheme. Default value is 'bearer'.
* @param {object} [options.tokenCache] The token cache. Default value is null.
* @param {object} [options.tokenCache] The token cache. Default value is the MemoryCache object from adal.
*/
function ApplicationTokenCredentials(clientId, domain, secret, options) {
if (!Boolean(clientId) || typeof clientId.valueOf() !== 'string') {
Expand All @@ -39,21 +39,25 @@ function ApplicationTokenCredentials(clientId, domain, secret, options) {
}

if (!options.environment) {
this.environment = AzureEnvironment.Azure;
} else {
this.environment = options.environment;
options.environment = AzureEnvironment.Azure;
}

if (!options.authorizationScheme) {
this.authorizationScheme = 'Bearer';
} else {
this.authorizationScheme = options.authorizationScheme;
options.authorizationScheme = Constants.HeaderConstants.AUTHORIZATION_SCHEME;
}


if (!options.tokenCache) {
options.tokenCache = new adal.MemoryCache();
}

this.environment = options.environment;
this.authorizationScheme = options.authorizationScheme;
this.tokenCache = options.tokenCache;
this.clientId = clientId;
this.domain = domain;
this.secret = secret;
var authorityUrl = this.environment.activeDirectoryEndpointUrl + this.domain;
this.context = new adal.AuthenticationContext(authorityUrl, this.environment.validateAuthority, this.tokenCache);
}

/**
Expand All @@ -65,10 +69,7 @@ function ApplicationTokenCredentials(clientId, domain, secret, options) {
*/
ApplicationTokenCredentials.prototype.signRequest = function (webResource, callback) {
var self = this;
var authorityUrl = self.environment.activeDirectoryEndpointUrl + self.domain;
var context = new adal.AuthenticationContext(authorityUrl, self.environment.validateAuthority, self.tokenCache);

context.acquireTokenWithClientCredentials(self.environment.activeDirectoryResourceId, self.clientId, self.secret, function (err, result) {
self.context.acquireTokenWithClientCredentials(self.environment.activeDirectoryResourceId, self.clientId, self.secret, function (err, result) {
if (err) {
return callback(new Error('Failed to acquire token for application. \n' + err));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

var util = require('util');
var msrest = require('ms-rest');
var adal = require('adal-node');
var Constants = msrest.Constants;

var azureConstants = require('../constants');
var AzureEnvironment = require('../azureEnvironment');

/**
* Creates a new DeviceTokenCredentials object that gets a new access token using userCodeInfo (contains user_code, device_code)
* for authenticating user on device.
*
* When this credential is used, the script will provide a url and code. The user needs to copy the url and the code, paste it
* in a browser and authenticate over there. If successful, the script will get the access token.
*
* @constructor
* @param {object} [options] Object representing optional parameters.
* @param {string} [options.username] The user name for account in the form: 'user@example.com'.
* @param {AzureEnvironment} [options.environment] The azure environment to authenticate with. Default environment is "Azure" popularly known as "Public Azure Cloud".
* @param {string} [options.domain] The domain or tenant id containing this application. Default value is 'common'
* @param {string} [options.clientId] The active directory application client id.
* See {@link https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-dotnet/ Active Directory Quickstart for .Net}
* for an example.
* @param {string} [options.authorizationScheme] The authorization scheme. Default value is 'bearer'.
* @param {object} [options.tokenCache] The token cache. Default value is the MemoryCache object from adal.
*/
function DeviceTokenCredentials(options) {
if (!options) {
options = {};
}

if (!options.username) {
options.username = 'user@example.com';
}

if (!options.environment) {
options.environment = AzureEnvironment.Azure;
}

if (!options.domain) {
options.domain = azureConstants.AAD_COMMON_TENANT;
}

if (!options.clientId) {
options.clientId = azureConstants.DEFAULT_ADAL_CLIENT_ID;
}

if (!options.authorizationScheme) {
options.authorizationScheme = Constants.HeaderConstants.AUTHORIZATION_SCHEME;
}

if (!options.tokenCache) {
options.tokenCache = new adal.MemoryCache();
}

this.username = options.username;
this.environment = options.environment;
this.domain = options.domain;
this.clientId = options.clientId;
this.authorizationScheme = options.authorizationScheme;
this.tokenCache = options.tokenCache;
var authorityUrl = this.environment.activeDirectoryEndpointUrl + this.domain;
this.context = new adal.AuthenticationContext(authorityUrl, this.environment.validateAuthority, this.tokenCache);
}

DeviceTokenCredentials.prototype.retrieveTokenFromCache = function (callback) {
var self = this;
self.context.acquireToken(self.environment.activeDirectoryResourceId, self.username, self.clientId, function (err, result) {
if (err) return callback(err);
return callback(null, result.tokenType, result.accessToken);
});
};


/**
* Signs a request with the Authentication header.
*
* @param {webResource} The WebResource to be signed.
* @param {function(error)} callback The callback function.
* @return {undefined}
*/
DeviceTokenCredentials.prototype.signRequest = function (webResource, callback) {
return this.retrieveTokenFromCache(function(err, scheme, token) {
if (err) return callback(err);
webResource.headers[Constants.HeaderConstants.AUTHORIZATION] = util.format('%s %s', scheme, token);
return callback(null);
});
};

module.exports = DeviceTokenCredentials;
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ var AzureEnvironment = require('../azureEnvironment');
* @param {string} domain The domain or tenant id containing this application.
* @param {string} username The user name for the Organization Id account.
* @param {string} password The password for the Organization Id account.
* @param {string} clientRedirectUri The Uri where the user will be redirected after authenticating with AD.
* @param {object} [options] Object representing optional parameters.
* @param {AzureEnvironment} [options.environment] The azure environment to authenticate with.
* @param {string} [options.authorizationScheme] The authorization scheme. Default value is 'bearer'.
* @param {object} [options.tokenCache] The token cache. Default value is null.
* @param {object} [options.tokenCache] The token cache. Default value is the MemoryCache object from adal.
*/
function UserTokenCredentials(clientId, domain, username, password, clientRedirectUri, options) {
function UserTokenCredentials(clientId, domain, username, password, options) {
if (!Boolean(clientId) || typeof clientId.valueOf() !== 'string') {
throw new Error('clientId must be a non empty string.');
}
Expand All @@ -40,35 +39,42 @@ function UserTokenCredentials(clientId, domain, username, password, clientRedire
if (!Boolean(password) || typeof password.valueOf() !== 'string') {
throw new Error('password must be a non empty string.');
}

if (!Boolean(clientRedirectUri) || typeof clientRedirectUri.valueOf() !== 'string') {
throw new Error('clientRedirectUri cannot be null.');
}

if (!options) {
options = {};
}

if (!options.environment) {
this.environment = AzureEnvironment.Azure;
} else {
this.environment = options.environment;
options.environment = AzureEnvironment.Azure;
}

if (!options.authorizationScheme) {
this.authorizationScheme = 'Bearer';
} else {
this.authorizationScheme = options.authorizationScheme;
options.authorizationScheme = Constants.HeaderConstants.AUTHORIZATION_SCHEME;
}

if (!options.tokenCache) {
options.tokenCache = new adal.MemoryCache();
}

this.environment = options.environment;
this.authorizationScheme = options.authorizationScheme;
this.tokenCache = options.tokenCache;
this.clientId = clientId;
this.domain = domain;
this.username = username;
this.password = password;
this.clientRedirectUri = clientRedirectUri;
var authorityUrl = this.environment.activeDirectoryEndpointUrl + this.domain;
this.context = new adal.AuthenticationContext(authorityUrl, this.environment.validateAuthority, this.tokenCache);
}

UserTokenCredentials.prototype.retrieveTokenFromCache = function (callback) {
var self = this;
self.context.acquireToken(self.environment.activeDirectoryResourceId, self.username, self.clientId, function (err, result) {
if (err) return callback(err);
return callback(null, result.tokenType, result.accessToken);
});
};

/**
* Signs a request with the Authentication header.
*
Expand All @@ -77,18 +83,10 @@ function UserTokenCredentials(clientId, domain, username, password, clientRedire
* @return {undefined}
*/
UserTokenCredentials.prototype.signRequest = function (webResource, callback) {
var self = this;
var authorityUrl = self.environment.activeDirectoryEndpointUrl + self.domain;
var context = new adal.AuthenticationContext(authorityUrl, self.environment.validateAuthority, self.tokenCache);

context.acquireTokenWithUsernamePassword(self.environment.activeDirectoryResourceId, self.username, self.password, self.clientId, function (err, result) {
if (err) {
return callback(new Error('Failed to acquire token. \n' + err));
}

webResource.headers[Constants.HeaderConstants.AUTHORIZATION] =
util.format('%s %s', self.authorizationScheme, result.accessToken);
callback(null);
return this.retrieveTokenFromCache(function(err, scheme, token) {
if (err) return callback(err);
webResource.headers[Constants.HeaderConstants.AUTHORIZATION] = util.format('%s %s', scheme, token);
return callback(null);
});
};

Expand Down
Loading

0 comments on commit 3cb1bec

Please sign in to comment.