Skip to content

Instantly share code, notes, and snippets.

@nicosabena
Last active March 3, 2022 18:48
Show Gist options
  • Save nicosabena/235c864935711712682baca19537634c to your computer and use it in GitHub Desktop.
Save nicosabena/235c864935711712682baca19537634c to your computer and use it in GitHub Desktop.
Auth0 rule to get user groups from Azure AD
// This rule will get the groups for users coming from Azure AD
// Auth0 already has the option to do that, but it (currently) won't work
// if the user is coming from a different directory than the directory
// where the app is registered (this can happen with multi-tenant apps).
// It uses the access_token provided by Azure AD, so this needs
// the 'Open ID Connect' protocol selected in the Azure AD connection.
//
// After the rule runs, you will have the 'groups' property in the user
// that you can use to add custom claims to the id_token.
//
// Currently, the rule just reads the top-level groups:
// waadClient.getGroupsForUserByObjectIdOrUpn(user.oid, function(err, groups) {
// but you can change it to read the extended user profile:
// waadClient.getUserByProperty('objectId', user.oid, { includeGroups: true, includeNestedGroups: false }, function(err, extUser) {
//
// To set up:
// - [Create a non-interactive client](https://auth0.com/docs/clients/client-settings/non-interactive)
// - Authorize the client to use Management API v2 with the `read:users` and `read:user_idp_tokens` scopes.
// - Configure your domain, client id and client secret in the rule (line 12-17)
// You can use the [configuration object](https://auth0.com/docs/rules/current#using-the-configuration-object)
// to store those values.
function (user, context, callback) {
'use strict';
if (context.connectionStrategy !== 'waad') {
// don't execute for non Azure AD connections
return callback(null, user, context);
}
if (user.groups) {
// dont't execute if user groups were already retrieved
return callback(null, user, context);
}
// you Auth0 domain
var AUTH0_DOMAIN = 'fabrikam.auth0.com';
// the client id and secret of a non-interactive client that is
// authorized to Management API v2 with scopes 'read:users' and 'read:user_idp_tokens'.
var APIV2_CLIENT_ID = 'xxxx';
var APIV2_CLIENT_SECRET = 'xxxx';
var auth0 = require('auth0@2.6.0');
var request = require('request');
var async = require('async');
function Waad10(options) {
options = options || {};
this.options = options;
this.baseUrl = 'https://graph.windows.net/';
this.apiVersion = '1.0';
if (!this.options.tenant) {
throw new Error('Must supply "tenant" id (16a88858-..2d0263900406) or domain (mycompany.onmicrosoft.com)');
}
if (!this.options.accessToken) {
throw new Error('Must supply "accessToken"');
}
}
Waad10.prototype.getUserByProperty = function (propertyName, propertyValue, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
var qs = {
"$filter": propertyName + " eq '" + propertyValue + "'",
"$top": 1
};
return this.__queryUsers(qs, options, function (err, users) {
if (err) return callback(err);
return callback(null, users[0]);
});
};
Waad10.prototype.__queryUsers = function (qs, options, callback) {
if (typeof options === 'function') {
callback = options;
options = options || {};
}
qs['api-version'] = this.apiVersion;
this.__request({
url: this.baseUrl + this.options.tenant + '/users',
qs: qs,
}, function (err, users) {
if (err || !users) return callback(err);
if (!options || !options.includeGroups) return callback(null, users);
async.forEach(users, function (user, cb) {
var groupsCallback = function (err, groups) {
if (err) return callback(err);
user.groups = groups;
cb();
};
if (options.includeNestedGroups) {
this.__queryNestedUserGroups(user.objectId, groupsCallback);
} else {
this.__queryUserGroup(user.objectId, groupsCallback);
}
}.bind(this), function (err) {
if (err) return callback(err);
return callback(null, users);
});
}.bind(this));
};
Waad10.prototype.__queryUserGroup = function (objectIdOrUpn, cb) {
var qs = {
"api-version": this.apiVersion
// Uncomment if you want to change the default max of 100 groups returned
//,"$top": 250
};
this.__request({
url: this.baseUrl + this.options.tenant + '/users/' + objectIdOrUpn + '/memberOf',
qs: qs
}, cb);
};
Waad10.prototype.__queryNestedUserGroups = function (objectIdOrUpn, cb) {
var qs = {
"api-version": this.apiVersion
// Uncomment if you want to change the default max of 100 groups returned
//,"$top": 250
};
this.__request({
url: this.baseUrl + this.options.tenant + '/users/' + objectIdOrUpn + '/getMemberGroups',
qs: qs,
method: 'POST',
json: { "securityEnabledOnly": false }
}, function (err, groups) {
if (err || !groups) return cb(err);
return this.__getObjectsByObjectIds(groups, cb);
}.bind(this));
};
Waad10.prototype.__getObjectsByObjectIds = function (objectIds, cb) {
var qs = {};
qs['api-version'] = '1.6';
this.__request({
url: this.baseUrl + this.options.tenant + '/getObjectsByObjectIds',
qs: qs,
method: 'POST',
json: {
"objectIds": objectIds,
"types": ["group"]
}
}, cb);
};
Waad10.prototype.__request = function (options, callback) {
var headers = {
'Authorization': 'Bearer ' + this.options.accessToken,
};
request({
url: options.url,
qs: options.qs || {},
method: options.method || 'GET',
json : options.json,
headers: headers
}, function(err, resp, body) {
if (err) return callback(err, null);
if (resp.statusCode !== 200) {
return callback(new Error(body), null);
}
var array = body;
if (typeof body === 'string'){
try {
array = JSON.parse(body);
} catch (e){
return callback(new Error('Failed to parse response from graph API'));
}
}
if (!array && array.value && array.value.length === 0)
return callback();
return callback(null, array.value);
}.bind(this));
};
Waad10.prototype.getGroupsForUserByObjectIdOrUpn = function(objectIdOrUpn, callback) {
return this.__queryUserGroup(objectIdOrUpn, callback);
};
function getManagementApiAccessToken(cb) {
var cacheKey = "apiv2TokenToReadUserIdpToken";
var cachedToken = global[cacheKey];
if (cachedToken) {
if (cachedToken.expirationDate > new Date()) {
console.log("Reusing cached token that expires in "+ cachedToken.expirationDate);
return cb(null, cachedToken.token);
}
console.log('Cached token found but expired.');
} else {
console.log('Cached token not found.');
}
var authClient = new auth0.AuthenticationClient({
domain: AUTH0_DOMAIN,
clientId: APIV2_CLIENT_ID,
clientSecret: APIV2_CLIENT_SECRET
});
console.log("Getting new api v2 access token");
authClient.clientCredentialsGrant({
audience: 'https://' + AUTH0_DOMAIN + '/api/v2/'
}, function (err, response) {
if (err) {
return cb(err);
}
var expirationDate = new Date();
expirationDate.setSeconds(expirationDate.getSeconds() + response.expires_in - 60);
// store token in cache
console.log("Storing token, expires in "+ expirationDate);
global[cacheKey] = {
expirationDate: expirationDate,
token: response.access_token
};
cb(null, response.access_token);
});
}
function getWaadAccessToken(apiToken, cb) {
var management = new auth0.ManagementClient({
token: apiToken,
domain: AUTH0_DOMAIN
});
management.users.get({ id: user.user_id })
.then(function (read_user) {
var waadIdentity = read_user.identities.find(function(identity) { return identity.provider === 'waad';});
if (waadIdentity) {
if (waadIdentity.access_token) {
return cb(null, waadIdentity.access_token);
} else {
return cb(new Error("Could not find waad access token."));
}
} else {
return cb(new Error("Could not find waad identity."));
}
})
.catch(function (err) {
return cb(err);
});
}
getManagementApiAccessToken(function (err, token) {
if (err) {
return callback(err);
}
getWaadAccessToken(token, function (err, waadToken) {
if (err) {
return callback(err);
}
var waadClient = new Waad10({ accessToken: waadToken, tenant: user.tenantid });
waadClient.getGroupsForUserByObjectIdOrUpn(user.oid, function(err, groups) {
if (err) {
return callback(err);
}
user.groups = [];
if (groups) {
groups.forEach(function (group) {
user.groups.push(group.displayName);
});
}
callback(null, user, context);
});
});
});
}
@dhmistry3
Copy link

How do I get the group IDs? the user.groups is just a list of the names.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment