Name: auth0-multitenant-spa-api-sample
Owner: Auth0
Description: JQuery SPA + Node.js API with multi-tenant support
Created: 2015-11-09 17:44:35.0
Updated: 2018-05-16 04:58:31.0
Pushed: 2017-12-22 02:33:20.0
Homepage: null
Size: 614
Language: JavaScript
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
This sample shows how to implement a multi-tenant scenario, where:
You can read more about multi-tenant scenarios here: https://auth0.com/docs/saas-apps.
The API is based on this sample: https://github.com/auth0/multitenant-jwt-auth.
The SPA is able to:
The API is able to:
The client is a node.js express app. The only code executing server side is the one for:
loginConfig
that will be used for authenticating the users.tion getTenantFromDomain(host){
r re = new RegExp(/(\w+)-yourcompany.com$/);
r matches = host.match(re);
(matches && matches.length > 1){
return matches[1];
turn null;
get('/', function(req, res, next) {
r tenantName = getTenantFromDomain(req.hostname);
(!tenantName) return next('Invalid domain: ' + req.hostname);
r tenantConfig = _.find(tenants,'name',tenantName);
(!tenantConfig) return next('Invalid Tenant ' + tenantName);
r loginConfig = {
auth0Domain:tenantConfig.auth0Domain,
auth0ClientId:tenantConfig.auth0ClientId
s.render('index', loginConfig);
Once the initial page is rendered, the app continues as a JQuery Single Page App. It uses the auth0Domain
and auth0ClientId
obtained from server side to configure auth0.js, which will be used to perform the Login.
The access_token obtained during login is saved to localStorage and will be used later to invoke the API. It is also used to fetch the user's profile.
The custom API uses express-jwt for validating JWTs.
expressJwt = require('express-jwt');
express = require('express');
app = express();
use('/api', expressJwt({
cret: secretCallback,
gorithms: [ 'HS256','RS256'] //the 2 algorithms supported by Auth0
Because of the multi-tenant support, the secret used to sign tokens is not static and hence a secretCallback
is used. The secretCallback supports verifying tokens signed with HS256 algorithm (clientId) and RS256 (asymmetric keys). It uses an LRU cache to store the secrets.
tion secretCallback (req, header, payload, cb){
var cacheKey = payload.iss + '|' + payload.aud ;
r cachedSecret = secretsCache.get(cacheKey);
(cachedSecret) {
return cb(null, cachedSecret);
r tenant = data.getTenantByIssuer(payload.iss);
(!tenant) {
return cb(new Error('Invalid issuer '+payload.iss));
itch (header.alg) {
case 'HS256': //client secret
var secret = new Buffer(tenant.secret, 'base64');
secretsCache.set(cacheKey, secret);
return cb(null, secret);
case 'RS256': // asymmetric keys
var url = payload.iss + '.well-known/jwks.json';
request.get(url, { json: true, strictSSL: false }, function (err, resp, jwks) {
if (err) {
return cb(err);
}
if (resp.statusCode !== 200) {
return cb(new Error('Failed to obtain JWKS from ' + payload.iss));
}
var key = _.find(jwks.keys, function(key) {
return key.kid == header.kid;
});
if (!key) {
return cb(new Error('Failed to obtain signing key used by ' + payload.iss));
}
var publicKey = certToPEM(key.x5c[0]);
secretsCache.set(cacheKey, publicKey);
return cb(null, publicKey);
});
break;
default:
return cb(new Error('Unsupported JWT algorithm: ' + header.alg));
The API provides a sample secured endpoint for getting a list of users specific for the tenant:
get('/api/users',
nantMiddleware(), //adds tenant's name to req from JWT's issuer claim
nction (req, res, next) {
data.getUsersByTenantIdentifier(req.tenantName, function(err, users){
if (err) return next(err);
res.json(users);
});
The tenant middleware adds the tenantName to the request. It obtains the tenant from the tenant store, using the JWT's issuer as a key:
data =require('./data');
le.exports = function(app){
turn function addTenant(req, res, next){
var tenant = data.getTenantByIssuer(req.user.iss);
req.tenantName = tenant.name;
next();
In order to emulate a multi-tenant environment, add the following entries to your machine's hosts file:
0.0.1 tenant1-yourcompany.com
0.0.1 tenant2-yourcompany.com
Each tenant needs its own Auth0 account. If the tenant already has an account it can use the existing one. Otherwise you can provision an account for him. You can create new accounts from the Auth0 master account, under the user's menu > New Account option.
You can name each tenant as tenantname-yourcompany.auth0.com
.
For each of the Auth0 accounts:
Make sure there is at least one Auth0 Application and take note of its clientId.
Make sure there is at least one Idenity Provider enabled for users to login.
Take note of the Applications ClientSecret. It will be used by the API to validate the tokens.
Alternatively, if the client doesn't want to share the secret, the API can validate tokens using the public key. In this case make sure they selected RS256 under Show Advanced Settings > JsonWebToken Token Signature Algorithm
Add the SPA domain to the Allowed Callback URLs. i.e: http://tenant1-yourcompany.com:3000
Create a tenants.js file using tenants-sample.js as a template. For each tenant provide:
Example with tenant1 using its own auth0 domain and using asymetric keys and tenant2 having an auth0 account provisioned by your company and using the client secret:
le.exports = [
name: 'tenant1',
auth0Domain: 'tenant1-custom-domain.auth0.com',
auth0ClientId:'6Y97vVD...C0sxSebDnw7R'
name: 'tenant2',
auth0Domain: 'tenant2-yourcompany.auth0.com',
auth0ClientId:'VIRUMI46...lU8f7d62s4',
secret: 'TUtF1F28Tage...SIMPhdQZuSy'
npm install
npm start
npm install
npm start
http://tenant1-yourcompany.com:3000
, login with tenant1's crentials and call the API. You should see the data associated with tenant1.http://tenant2-yourcompany.com:3000
and do the same for tenant2.Auth0 helps you to:
If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The Responsible Disclosure Program details the procedure for disclosing security issues.
This project is licensed under the MIT license. See the LICENSE file for more info.