diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 00000000..4fe74bf8 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,4 @@ +{ + "directory": "themes/default/public/vendor/bower_components", + "analytics": false +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7637b478..d16df628 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ # Node.js node_modules npm-debug.log +vendor +config.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..91b4b90a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM node + +ADD . /usr/app +WORKDIR /usr/app + +EXPOSE 5050 + +CMD db="" node app.js diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..5ec68a92 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +REPORTER ?= list +TEST_DB=mongodb://localhost:27017/auth11-tests +TEST_NODE_ENV=test + +test: node_modules + @db=$(TEST_DB) PORT=5050 NODE_ENV=$(TEST_NODE_ENV) BASE_URL=/docs NODE_TLS_REJECT_UNAUTHORIZED=0 \ + ./node_modules/.bin/mocha --reporter $(REPORTER) + +node_modules: + @npm i + +test-shrinkwrap-status: + @./node_modules/.bin/npm-shrinkwrap + @git status | grep npm-shrinkwrap.json ; test "$$?" -eq 1 + @echo shrinkwrap is okay + +test-sec-deps: + @./node_modules/.bin/nsp audit-shrinkwrap + +.PHONY: test diff --git a/README.md b/README.md index cc23ec6c..7d8e60ef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,43 @@ -To run: +Node version: 0.10.30 +NPM version: 1.4.21 - node app.js +## Development +Vagrant install production dependencies but not dev dependencies. Use this: + +``` +nave use 0.10.30 +npm i +``` + +## Installing or Updating dependencies + +**Do not update npm-shrinkwrap.json by hand** + +Use this procedure: +``` + +npm i foo@~1.2.3 --save +npm run shrinkwrap +``` +``` +make test +``` + +If you want verbose logs use: + +``` +make test TEST_DEBUG_LEVEL=debug +``` + +## Testing the shrinkwrap status + +``` +make test-shrinkwrap-status +``` + +## Testing dependencies (node-security) + +``` +make test-sec-deps +``` \ No newline at end of file diff --git a/app.js b/app.js index 65ab46df..c0400737 100644 --- a/app.js +++ b/app.js @@ -1,12 +1,28 @@ -var markdocs = require('markdocs'); -var nconf = require('nconf'); -var path = require('path'); -var express = require('express'); -var http = require('http'); -var https = require('https'); +var cluster = require('cluster'); + +if (cluster.isMaster && !module.parent) { + return require('./master'); +} + +/** + * Module dependencies. + */ + +var redirect = require("express-redirect"); +var prerender = require('prerender-node'); var passport = require('passport'); +var markdocs = require('markdocs'); +var header = require('web-header'); +var express = require('express'); +var nconf = require('nconf'); +var https = require('https'); +var http = require('http'); +var path = require('path'); +var fs = require('fs'); -var app = express(); +var default_callback = require('./lib/default_callback'); + +var app = redirect(express()); nconf .use("memory") @@ -14,73 +30,161 @@ nconf .env() .file({ file: process.env.CONFIG_FILE || path.join(__dirname, "config.json")}) .defaults({ - 'db' : 'mongodb://localhost:27017/auth11', 'sessionSecret': 'auth11 secret string', 'COOKIE_SCOPE': process.env.NODE_ENV === 'production' ? '.auth0.com' : null, + 'COOKIE_NAME': 'auth0l', + 'CURRENT_TENANT_COOKIE': 'current_tenant', 'DOMAIN_URL_SERVER': '{tenant}.auth0.com:3000', 'DOMAIN_URL_APP': 'localhost:8989', - 'DOMAIN_URL_SDK': 'localhost:3000', - 'DOMAIN_URL_DOCS': 'https://localhost:5000', - 'WIDGET_FALLBACK_CLIENTID': 'aCbTAJNi5HbsjPJtRpSP6BIoLPOrSj2C' + 'DOMAIN_URL_SDK': 'login-dev.auth0.com:3000', + 'PACKAGER_URL': 'http://localhost:3001', + 'DOMAIN_URL_DOCS': 'https://localhost:5050', + 'DOMAIN_URL_API2_EXPLORER': 'login0.myauth0.com', + 'WIDGET_FALLBACK_CLIENTID': 'aCbTAJNi5HbsjPJtRpSP6BIoLPOrSj2C', + 'LEGACY_WIDGET_URL': 'https://cdn.auth0.com/w2/auth0-widget-5.2.min.js', + 'LOGIN_WIDGET_URL': 'https://cdn.auth0.com/js/lock-6.2.min.js', + 'AUTH0JS_URL': 'https://cdn.auth0.com/w2/auth0-1.6.4.min.js', + 'AUTH0_ANGULAR_URL': 'http://cdn.auth0.com/w2/auth0-angular-1.1.js', + 'SENSITIVE_DATA_ENCRYPTION_KEY': '0123456789', + 'HMAC_ENCRYPTION_KEY': 'abcdefghij', + 'PUBLIC_ALLOWED_TUTORIALS': '/adldap-auth?,/adldap-x?,/adfs?', + 'AUTH0_TENANT': 'auth0-dev', + 'AUTH0_CLIENT_ID': 'aCbTAJNi5HbsjPJtRpSP6BIoLPOrSj2C', + 'PRERENDER_ENABLED': false, + 'BASE_URL': '' }); -var connections = require('./lib/connections'); +// after configuration so values are available +var middlewares = require('./lib/middlewares'); -var getDb = require('./lib/data'); -var sessionStore = require('./lib/sessionStore'); +if (nconf.get('db')) { + console.log('db is ' + nconf.get('db')); +} + +if (nconf.get('COOKIE_NAME') !== 'auth0l') { + nconf.set('CURRENT_TENANT_COOKIE', nconf.get('COOKIE_NAME') + '_current_tenant'); +} + +if (!nconf.get('LOGIN_WIDGET_URL')) { + nconf.set('LOGIN_WIDGET_URL', 'https://' + nconf.get('DOMAIN_URL_SDK') + '/w2/auth0-widget.min.js'); +} + +if (!nconf.get('AUTH0JS_URL')) { + nconf.set('AUTH0JS_URL', 'https://' + nconf.get('DOMAIN_URL_SDK') + '/w2/auth0.min.js'); +} + +if (!nconf.get('AUTH0_DOMAIN') && nconf.get('AUTH0_TENANT') && nconf.get('DOMAIN_URL_SERVER')) { + nconf.set('AUTH0_DOMAIN', nconf.get('DOMAIN_URL_SERVER').replace('{tenant}', nconf.get('AUTH0_TENANT'))); +} + +if (nconf.get('PRERENDER_SERVICE_URL')) { + prerender.set('prerenderServiceUrl', nconf.get('PRERENDER_SERVICE_URL')); +} + +if (nconf.get('PRERENDER_TOKEN')) { + prerender.set('prerenderToken', nconf.get('PREPRENDER_TOKEN')); +} + +if (nconf.get('PRERENDER_PROTOCOL')) { + prerender.set('protocol', nconf.get('PRERENDER_PROTOCOL')); +} + +var connections = require('./lib/connections'); +var clients = require('./lib/clients'); require('./lib/setupLogger'); var winston = require('winston'); passport.serializeUser(function(user, done) { - done(null, user.id); - }); + if (!nconf.get('db')) { + return done(null, user); + } + done(null, user.id); +}); passport.deserializeUser(function(id, done) { + if (!nconf.get('db')) { + return done(null, id); + } + + var getDb = require('./lib/data'); getDb(function(db){ var userColl = db.collection("tenantUsers"); userColl.findOne({id: id}, done); }); }); -//force https -app.configure('production', function(){ - - this.use(function(req, res, next){ - if (nconf.get('dontForceHttps') || req.originalUrl === '/test') return next(); +(function(){ + this.set("view engine", "jade"); + + if (nconf.get('NODE_ENV') === 'production') { + this.use(function(req, res, next){ + if (nconf.get('dontForceHttps') || req.originalUrl === '/test') return next(); - if(req.headers['x-forwarded-proto'] !== 'https') - return res.redirect(nconf.get('DOMAIN_URL_DOCS') + req.url); - + if(req.headers['x-forwarded-proto'] !== 'https') + return res.redirect(nconf.get('DOMAIN_URL_DOCS') + req.url); + + next(); + }); + } + + if (nconf.get('PRERENDER_ENABLED')) { + // Add swiftype UserAgent bot + prerender.crawlerUserAgents.push('Swiftbot'); + prerender.crawlerUserAgents.push('Slackbot-LinkExpanding'); + // add prerender middleware + this.use(prerender); + } + + this.use('/test', function (req, res) { + res.send(200); + }); + + this.use(nconf.get('BASE_URL') + '/test', function (req, res) { + res.send(200); + }); + + this.use(function (req, res, next) { + if (!nconf.get('BASE_URL') || req.url === '/') return next(); + req.url = req.url.replace(/\/$/,''); next(); }); -}); -app.configure(function(){ - this.set("view engine", "jade"); this.use(express.logger('dev')); - this.use('/test', function (req, res) { - return res.json(200, process.memoryUsage()); - }); + + this.use(middlewares.cors); this.use(express.cookieParser()); - this.use(express.session({ secret: nconf.get("sessionSecret"), store: sessionStore, key: "auth0l", cookie: { - domain: nconf.get('COOKIE_SCOPE'), - path: '/', - httpOnly: true, - maxAge: null, - secure: !nconf.get('dontForceHttps') && nconf.get('NODE_ENV') === 'production' - }})); + this.use(express.session({ + secret: nconf.get("sessionSecret"), + store: require('./lib/sessionStore'), + key: nconf.get('COOKIE_NAME'), + cookie: { + domain: nconf.get('COOKIE_SCOPE'), + path: '/', + httpOnly: true, + maxAge: null, + secure: !nconf.get('dontForceHttps') && nconf.get('NODE_ENV') === 'production' + } + })); this.use(express.favicon()); this.use(express.logger('dev')); - this.use(express.bodyParser()); + this.use(express.json()); + this.use(express.urlencoded()); + + // warning this cause an Internal Server Error + // this.use(require('method-override')); this.use(express.methodOverride()); + //////////////////////////////////////// + this.use(passport.initialize()); this.use(passport.session()); + this.use(require('./lib/set_current_tenant')); + this.use(require('./lib/set_user_is_owner')); this.use(this.router); -}); +}).call(app); app.get('/ticket/step', function (req, res) { if (!req.query.ticket) return res.send(404); @@ -91,23 +195,33 @@ app.get('/ticket/step', function (req, res) { }); }); +app.get(nconf.get('BASE_URL') + '/switch', function (req, res) { + req.session.current_tenant = req.query.tenant; + res.redirect(nconf.get('BASE_URL') || '/'); +}); + var defaultValues = function (req, res, next) { res.locals.account = {}; + res.locals.account.clientParam = ''; res.locals.account.userName = ''; res.locals.account.appName = 'YOUR_APP_NAME'; res.locals.account.tenant = 'YOUR_TENANT'; res.locals.account.namespace = 'YOUR_NAMESPACE'; res.locals.account.clientId = 'YOUR_CLIENT_ID'; res.locals.account.clientSecret = 'YOUR_CLIENT_SECRET'; - res.locals.account.callback = 'http://YOUR_APP/callback'; + res.locals.account.callback = default_callback.get(req) || 'http://YOUR_APP/callback'; + + res.locals.base_url = nconf.get('DOMAIN_URL_DOCS'); + // var escape = nconf.get('BASE_URL').replace(/\/([^\/]*)/ig, '/..'); + res.locals.webheader = header({ base_url: 'https://auth0.com' }); next(); }; var embedded = function (req, res, next) { res.locals.embedded = false; + if (req.query.e || req.query.callback) { - res.locals.base_url = nconf.get('DOMAIN_URL_DOCS'); res.locals.embedded = true; } @@ -119,127 +233,296 @@ var embedded = function (req, res, next) { }; var overrideIfAuthenticated = function (req, res, next) { - winston.debug('user', req.user); - - if (!req.user || !req.user.tenant) + if (!req.user || !req.user.tenant) { return next(); + } - var queryDoc = {tenant: req.user.tenant, global: false}; + res.locals.user = { + tenant: req.user.tenant, + tenants: req.user.tenants + }; - if(req.session.selectedClient){ + var queryDoc = { + tenant: req.user.tenant + }; + + if (req.session.selectedClient) { queryDoc.clientID = req.session.selectedClient; } - getDb(function(db){ - - db.collection('clients').find(queryDoc).toArray(function(err, clients){ - if(err) { - winston.error("error: " + err); - return next(err); + clients.find(queryDoc, function (err, clients) { + if (err) { + winston.error("error: " + err); + return next(err); + } + + // filter user's clients + if (!req.user.is_owner) { + clients = clients.filter(function (c) { + return c.owners && ~c.owners.indexOf(req.user.id); + }); + } + + var globalClient = {}, nonGlobalClients = []; + + clients.forEach(function (client) { + if (client.global) { + globalClient = client; + return; } - - if (clients.length === 0) return next(); - - res.locals.account.loggedIn = true; - res.locals.account.clients = clients; - var client = clients[0]; - - winston.debug('client found'); - res.locals.account.appName = client.name && client.name.trim !== '' ? client.name : 'Your App'; - res.locals.account.userName = req.user.name; - res.locals.account.namespace = nconf.get('DOMAIN_URL_SERVER').replace('{tenant}', client.tenant); - res.locals.account.tenant = client.tenant; - res.locals.account.clientId = client.clientID; - res.locals.account.clientSecret = client.clientSecret; - res.locals.account.callback = client.callback; - next(); + + nonGlobalClients.push(client); }); + + res.locals.account = res.locals.account || {}; + res.locals.account.loggedIn = true; + res.locals.account.userName = req.user.name; + + res.locals.account.namespace = nconf.get('DOMAIN_URL_SERVER').replace('{tenant}', req.user.tenant); + res.locals.account.tenant = req.user.tenant; + + res.locals.account.globalClientId = globalClient.clientID || 'YOUR_GLOBAL_CLIENT_ID'; + res.locals.account.globalClientSecret = globalClient.clientSecret; + + if (nonGlobalClients.length === 0) { + return next(); + } + + res.locals.account.clients = nonGlobalClients; + + var client = nonGlobalClients[0]; + res.locals.account.appName = client.name && client.name.trim !== '' ? client.name : 'Your App'; + res.locals.account.clientId = client.clientID; + res.locals.account.clientParam = '&clientId=' + client.clientID; + res.locals.account.clientSecret = client.clientSecret; + res.locals.account.callback = client.callback; + + next(); }); }; -var overrideIfClientInQs = function (req, res, next) { - if (!req.query || !req.query.a) - return next(); +var overrideIfClientInQsForPublicAllowedUrls = function (req, res, next) { - getDb(function(db){ - db.collection('clients').findOne({clientID: req.query.a}, function(err, client){ - if(err) { - console.error("error: " + err); - return next(err); - } + var allowed = nconf.get('PUBLIC_ALLOWED_TUTORIALS').split(',').some(function (allowedUrl) { + return req.originalUrl.indexOf(allowedUrl) === 0; + }); - if(!client) { - return res.send(404, 'client not found'); - } - - res.locals.account.appName = client.name && client.name.trim !== '' ? client.name : 'Your App'; - res.locals.account.namespace = nconf.get('DOMAIN_URL_SERVER').replace('{tenant}', client.tenant); - res.locals.account.tenant = client.tenant; - res.locals.account.clientId = client.clientID; - res.locals.account.clientSecret = client.clientSecret; - res.locals.account.callback = client.callback; + if (!allowed) return next(); + if (!req.query || !req.query.a) return next(); - next(); - }); + clients.findByClientId(req.query.a, { signingKey: 0 }, function (err, client) { + if (err) { return next(err); } + if (!client) { + return res.send(404, 'client not found'); + } + + res.locals.account.appName = client.name && client.name.trim !== '' ? client.name : 'Your App'; + res.locals.account.namespace = nconf.get('DOMAIN_URL_SERVER').replace('{tenant}', client.tenant); + res.locals.account.tenant = client.tenant; + res.locals.account.clientId = client.clientID; + res.locals.account.clientParam = '&clientId=' + client.clientID; + res.locals.account.clientSecret = 'YOUR_CLIENT_SECRET'; // it's a public url (don't share client secret) + res.locals.account.callback = client.callback; + res.locals.connectionName = req.query.conn; + + next(); + }); +}; + +var overrideIfClientInQs = function (req, res, next) { + if (!req.query || !req.query.a) { return next(); } + if (!req.user || !req.user.tenant) { return next(); } + + clients.findByTenantAndClientId(req.user.tenant, req.query.a, function (err, client) { + if (err) { return next(err); } + if (!client) { return res.send(404, 'client not found'); } + if (!req.user.is_owner && (!client.owners || client.owners.indexOf(req.user.id) < 0)) { + return res.send(401); + } + + res.locals.account.appName = client.name && client.name.trim !== '' ? client.name : 'Your App'; + res.locals.account.namespace = nconf.get('DOMAIN_URL_SERVER').replace('{tenant}', client.tenant); + res.locals.account.tenant = client.tenant; + res.locals.account.clientId = client.clientID; + res.locals.account.clientParam = '&clientId=' + client.clientID; + res.locals.account.clientSecret = client.clientSecret; + res.locals.account.callback = client.callback; + res.locals.connectionName = req.query.conn; + + next(); }); }; var appendTicket = function (req, res, next) { res.locals.ticket = 'YOUR_TICKET'; res.locals.connectionDomain = 'YOUR_CONNECTION_NAME'; + res.locals.connectionName = res.locals.connectionName || 'YOUR_CONNECTION_NAME'; if (!req.query.ticket) return next(); connections.findByTicket(req.query.ticket, function (err, connection) { if (err) return res.send(500); if (!connection) return res.send(404); res.locals.ticket = req.query.ticket; res.locals.connectionDomain = connection.options.tenant_domain; + res.locals.connectionName = connection.name; next(); }); }; -var docsapp = new markdocs.App(__dirname, '', app); -docsapp.addPreRender(function(req, res, next) { console.log(res.locals); next();}); +/** + * Add quickstart collections for initialization + * with server matching versioning for SEO and sitemap.xml + */ + +var collections = require('./lib/quickstart-collections'); + +var quickstartCollections = function (req, res, next) { + if (res.locals.quickstart != null) return next(); + res.locals.quickstart = {}; + res.locals.quickstart.apptypes = collections.apptypes; + res.locals.quickstart.clientPlatforms = collections.clientPlatforms; + res.locals.quickstart.nativePlatforms = collections.nativePlatforms; + res.locals.quickstart.hybridPlatforms = collections.hybridPlatforms; + res.locals.quickstart.serverPlatforms = collections.serverPlatforms; + res.locals.quickstart.serverApis = collections.serverApis; + next(); +}; + +/** + * Manage redirect 301 for deprecated links + * to point to new links or documents + */ + +require('./lib/redirects')(app); + +/** + * Register quickstart routes as an alias to index `/` + * So that the tutorial navigator gets to load + * quickstart collections and render + */ + +var quickstartRoutes = require('./lib/quickstart-routes'); + +quickstartRoutes.forEach(function(route) { + app.get(nconf.get('BASE_URL') + '/quickstart' + route, alias(nconf.get('BASE_URL') || '/')); +}); + +app.get(nconf.get('BASE_URL') + '/quickstart', alias(nconf.get('BASE_URL') || '/')); + +function alias(route) { + return function(req, res, next) { + req.url = route; + next(); + }; +} + +var includes = require('./lib/includes/includes'); +includes.init(path.join(__dirname, '/docs/includes')); + +/** + * Create and boot DocsApp as `Markdocs` app + */ + +var docsapp = new markdocs.App(__dirname, nconf.get('BASE_URL') || '', app); docsapp.addPreRender(defaultValues); +docsapp.addPreRender(includes.add); docsapp.addPreRender(overrideIfAuthenticated); docsapp.addPreRender(overrideIfClientInQs); +docsapp.addPreRender(overrideIfClientInQsForPublicAllowedUrls); docsapp.addPreRender(appendTicket); +docsapp.addPreRender(quickstartCollections); docsapp.addPreRender(embedded); docsapp.addPreRender(function(req,res,next){ - if(process.env.NODE_ENV === 'production') { - res.locals.uiURL = 'https://' + nconf.get('DOMAIN_URL_APP'); - res.locals.sdkURL = 'https://' + nconf.get('DOMAIN_URL_SDK'); - } else { - res.locals.uiURL = 'http://' + nconf.get('DOMAIN_URL_APP'); - res.locals.sdkURL = 'http://' + nconf.get('DOMAIN_URL_SDK'); + var scheme = process.env.NODE_ENV === 'production' ? 'https' : 'http'; + + res.locals.uiURL = scheme + '://' + nconf.get('DOMAIN_URL_APP'); + res.locals.uiURLLoginCallback = res.locals.uiURL + '/callback'; + res.locals.sdkURL = scheme + '://' + nconf.get('DOMAIN_URL_SDK'); + + if (res.locals.account && res.locals.account.clientId) { + res.locals.uiAppSettingsURL = res.locals.uiURL + '/#/applications/' + res.locals.account.clientId + '/settings'; + res.locals.uiAppAddonsURL = res.locals.uiURL + '/#/applications/' + res.locals.account.clientId + '/addons'; + } + + function removeScheme(url) { + return url.slice(url.indexOf(':') + 1); + } + + // Auth0 client side Javascript URLs to use + res.locals.auth0js_url = nconf.get('AUTH0JS_URL'); + res.locals.auth0js_url_no_scheme = removeScheme(nconf.get('AUTH0JS_URL')); + + res.locals.auth0_angular_url = nconf.get('AUTH0_ANGULAR_URL'); + res.locals.auth0_angular_url_no_scheme = removeScheme(nconf.get('AUTH0_ANGULAR_URL')); + + res.locals.widget_url = nconf.get('LOGIN_WIDGET_URL'); + res.locals.widget_url_no_scheme = removeScheme(nconf.get('LOGIN_WIDGET_URL')); + + res.locals.hasCallback = res.locals.account && !!res.locals.account.callback; + + // defualt values + if (res.locals.account) { + res.locals.account.callback = res.locals.account.callback || + default_callback.get(req) || + 'http://YOUR_APP/callback'; } + next(); }); -docsapp.addPreRender(require('./sdk/middleware')); -require('./sdk/demos-routes')(app); - -if (!module.parent) { - var server; - if (process.env.NODE_ENV === 'production') { - server = http.createServer(app); - } else { - var options = { - key: fs.readFileSync('./localhost.key'), - cert: fs.readFileSync('./localhost.pem') - }; - - server = https.createServer(options, app) - .on('error', function (err) { - if(err.errno === 'EADDRINUSE'){ - console.log('error when running http server on port ', port, '\n', err.message); - process.exit(1); - } - }); +var lockGithub = require('./lib/lock-github'); + +docsapp.addPreRender(function(req, res, next) { + function save(err) { + if (err) return next(err); + res.locals.lock_readme = lockGithub.cache['README.md']; + next(); } - var port = process.env.PORT || 5000; - server.listen(port); - console.log('Server listening on https://localhost:' + port); -} else { - module.exports = docsapp; + if ('README.md' in lockGithub.cache) return save(null); + lockGithub('README.md', save); +}); + +docsapp.addPreRender(require('./lib/external/middleware')); +docsapp.addPreRender(require('./lib/external/api2-explorer-middleware')); +docsapp.addPreRender(require('./lib/sdk-snippets/login-widget/middleware')); +docsapp.addPreRender(require('./lib/sdk-snippets/login-widget2/middleware')); +docsapp.addPreRender(require('./lib/sdk-snippets/lock/middleware-browser')); +docsapp.addPreRender(require('./lib/sdk-snippets/lock/middleware')); +docsapp.addPreRender(middlewares.configuration); +docsapp.addExtension(require('./lib/extensions').lodash); +docsapp.addExtension(require('./lib/extensions').warningBlock); +require('./lib/sdk-snippets/lock/demos-routes')(app); +require('./lib/sdk-snippets/lock/snippets-routes')(app); +require('./lib/sdk-snippets/login-widget2/demos-routes')(app); +require('./lib/sdk-snippets/login-widget2/snippets-routes')(app); +require('./lib/sdk-snippets/login-widget/demos-routes')(app); +require('./lib/packager')(app, overrideIfAuthenticated); +require('./lib/sitemap')(app); + + +/** + * Export `docsapp` or boot a new https server + * with it + */ + +var server = http.createServer(app); + +var port = nconf.get('PORT') || 5050; +server.listen(port, function () { + console.log('Server listening on http://localhost:' + port); +}); + +var enableDestroy = require('server-destroy'); +enableDestroy(server); + +process.on('SIGTERM', function () { + server.destroy(function () { + process.exit(0); + }); +}); + +if (module.parent) { + module.exports.stop = function (cb) { + server.destroy(cb); + }; } diff --git a/bower.json b/bower.json new file mode 100644 index 00000000..c177d02e --- /dev/null +++ b/bower.json @@ -0,0 +1,22 @@ +{ + "name": "auth0-docs", + "version": "0.0.0", + "homepage": "https://github.com/auth0/docs", + "description": "Auth0 Documents", + "main": "app.js", + "authors": [ + "Auth0 " + ], + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "themes/default/public/vendor/bower_components", + "test", + "tests" + ], + "dependencies": { + "auth0-styleguide": "git@github.com:auth0/styleguide.git#0.5.6" + } +} diff --git a/build b/build new file mode 100755 index 00000000..5d75a99f --- /dev/null +++ b/build @@ -0,0 +1,22 @@ +#!/bin/sh + +set -e + +#install all deps +npm install + +#set the version number in the package json without a new commit. +mv .git .git-back +npm version $BUILD_NUMBER +mv .git-back .git + +#remove ignore files +find . -name ".npmignore" -o -name ".gitignore" -delete + +#create a tar.gz +bn=${PWD##*/} +tar -czf /tmp/auth0-docs-$BUILD_NUMBER.tgz --exclude=".git" --exclude="$bn/test" -C .. $bn/ +mv /tmp/auth0-docs-$BUILD_NUMBER.tgz ./auth0-docs-$BUILD_NUMBER.tgz + +#revert all changes +git checkout . \ No newline at end of file diff --git a/docs/37signals-clientid.md b/docs/37signals-clientid.md index 91fba586..904b6203 100644 --- a/docs/37signals-clientid.md +++ b/docs/37signals-clientid.md @@ -6,7 +6,7 @@ To configure a 37Signals OAuth2 connection you will need to register your Auth0 The first form consists of basic information about your app (name, website, logo): -![](img/37signals-register-1.png) +![](//cdn.auth0.com/docs/img/37signals-register-1.png) --- @@ -14,7 +14,7 @@ The first form consists of basic information about your app (name, website, logo Define which 37Signals applications you want access to, and complete the callback URL in Auth0: -![](img/37signals-register-2.png) +![](//cdn.auth0.com/docs/img/37signals-register-2.png) https://@@account.namespace@@/login/callback @@ -24,7 +24,7 @@ Define which 37Signals applications you want access to, and complete the callbac Once the application is registered, enter your new `Client Id` and `Client Secret` into the connection settings in Auth0. -![](img/37signals-register-4.png) +![](//cdn.auth0.com/docs/img/37signals-register-4.png) diff --git a/docs/ad.md b/docs/ad.md new file mode 100644 index 00000000..24876319 --- /dev/null +++ b/docs/ad.md @@ -0,0 +1,35 @@ +# Connecting Active Directory with Auth0 + +Auth0 integrates with Active Directory/LDAP through the __Active Directory/LDAP Connector__ that you install in your network. + +The __AD/LDAP Connector (1)__, is a bridge between your __Active Directory (2)__ and the __Auth0 Service (3)__. This bridge is necessary because AD is typically locked down to your internal network, and Auth0 is a cloud service running on a completely different context. + + + +You can install multiple instances of the connector for high availability and load balancing. Also, all connections are out-bound: from the connector to the Auth0 Server, so in general no changes to the firewall need to be applied. + +Configuring an AD/LDAP connection in Auth0 requires two simple steps: + +###1. Creating an AD/LDAP Connection in Auth0 + +The first step is creating a new Connection on the dashboard: + +__Connections > Enterprise > AD/LDAP__ + +![](https://cdn.auth0.com/docs/img/ldap-create.png) + +Name the connection and check whether you want `Kerberos` enabled for this connection. If you enable this, you need to enter the range of IP addresses from where `Kerberos` authentication will be enabled. These would typically be the intranet where `Kerberos` would work. + +In addition, the `Email domains` field, whitelists email suffixes that will be recognized before redirecting users to this particular AD/LDAP connection. + +![](https://cdn.auth0.com/docs/img/ldap-create-2.png) + +__Save__ the configuration. You are done on the Auth0 side! You will then be prompted to download the __AD/LDAP Connector__ to your machine. + +![](https://cdn.auth0.com/docs/img/ldap-create-3.png) + +> We ship different versions of the Connector to install it on multiple platforms: Windows and Linux. + +Keep the __TICKET URL__ at hand as you will need it later. + +[Continue to the instructions to install the connector.](@@env.BASE_URL@@/connector). diff --git a/docs/adfs.md b/docs/adfs.md index a6d8170c..65e47924 100644 --- a/docs/adfs.md +++ b/docs/adfs.md @@ -4,15 +4,23 @@ layout: doc.nosidebar --- # ADFS +This is the data you should give to the ADFS admin: + +* Realm Identifier: `urn:auth0:@@account.tenant@@` +* Endpoint: `https://@@account.namespace@@/login/callback` + +> If you want to use the [/oauth/ro](/auth-api#post--oauth-ro) endpoint make sure to enable `/adfs/services/trust/13/usernamemixed`. + ### Scripted setup -This script uses the [ADFS PowerShell SnapIn](http://technet.microsoft.com/en-us/library/adfs2-powershell-basics.aspx) to create and configure a Relying Party that will issue the following claims: __email__, __upn__, __given name__ and __surname__ for the authenticated user. + +For automated integration, this script uses the [ADFS PowerShell SnapIn](http://technet.microsoft.com/en-us/library/adfs2-powershell-basics.aspx) to create and configure a Relying Party that will issue the following claims: __email__, __upn__, __given name__ and __surname__ for the authenticated user. (new-object Net.WebClient -property @{Encoding = [Text.Encoding]::UTF8}).DownloadString("https://raw.github.com/auth0/adfs-auth0/master/adfs.ps1") | iex AddRelyingParty "urn:auth0:@@account.tenant@@" "https://@@account.namespace@@/login/callback" -Copy & Paste the script above on Windows PowerShell. +Copy & Paste the script above on Windows PowerShell. -![](img/adfs-script.png) +![](//cdn.auth0.com/docs/img/adfs-script.png) > You must run the script above as an administrator of your system. @@ -22,7 +30,7 @@ Copy & Paste the script above on Windows PowerShell. $realm = "urn:auth0:@@account.tenant@@"; $webAppEndpoint = "https://@@account.namespace@@/login/callback"; - + Add-PSSnapin Microsoft.Adfs.Powershell Add-ADFSRelyingPartyTrust -Name $realm -Identifier $realm -WSFedEndpoint $webAppEndpoint $rp = Get-ADFSRelyingPartyTrust -Name $realm @@ -30,16 +38,16 @@ Copy & Paste the script above on Windows PowerShell. #####2. Creates the rules to output the most common Active Directory attributes (email, UPN, given name, surname) $rules = @' - @RuleName = "Store: ActiveDirectory -> Mail (ldap attribute: mail), Name (ldap attribute: displayName), Name ID (ldap attribute: userPrincipalName), GivenName (ldap attribute: givenName), Surname (ldap attribute: sn)" - c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"] - => issue(store = "Active Directory", - types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", - "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", - "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", - "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", + @RuleName = "Store: ActiveDirectory -> Mail (ldap attribute: mail), Name (ldap attribute: displayName), Name ID (ldap attribute: userPrincipalName), GivenName (ldap attribute: givenName), Surname (ldap attribute: sn)" + c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"] + => issue(store = "Active Directory", + types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", + "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", + "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"), query = ";mail,displayName,userPrincipalName,givenName,sn;{0}", param = c.Value); '@ - + Set-ADFSRelyingPartyTrust –TargetName $realm -IssuanceTransformRules $rules $rSet = New-ADFSClaimRuleSet –ClaimRule '=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");' @@ -57,7 +65,7 @@ If you don't feel comfortable executing the script, you can follow these manual 4- Select `Enter data about the relying party manually` and click `Next` -![](img/adfs-importmanual.png) +![](//cdn.auth0.com/docs/img/adfs-importmanual.png) 5- Enter an arbitrary name (e.g. "@@account.appName@@") and click `Next` @@ -69,13 +77,13 @@ If you don't feel comfortable executing the script, you can follow these manual https://@@account.namespace@@/login/callback -![](img/adfs-url.png) - +![](//cdn.auth0.com/docs/img/adfs-url.png) + 9- Add a relying party identifier with the following value and click `Add` and then `Next` - urn:auth0:@@account.clientId@@ + urn:auth0:@@account.tenant@@ -![](img/adfs-identifier.png) +![](//cdn.auth0.com/docs/img/adfs-identifier.png) 10- Leave the default option (Permit all users...) and click `Next` @@ -85,7 +93,7 @@ If you don't feel comfortable executing the script, you can follow these manual 13- Leave the default option (Send LDAP Attributes as Claims) -![](img/adfs-sendldap.png) +![](//cdn.auth0.com/docs/img/adfs-sendldap.png) 14- Give the rule an arbitrary name that describes what it does. For example: @@ -93,6 +101,6 @@ If you don't feel comfortable executing the script, you can follow these manual 15- Select the following mappings and click `Finish` -![](img/adfs-claimrules.png) +![](//cdn.auth0.com/docs/img/adfs-claimrules.png) Yes, running the script is definitely easier. diff --git a/docs/adldap-authentication.md b/docs/adldap-authentication.md deleted file mode 100644 index 1eb25149..00000000 --- a/docs/adldap-authentication.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -title: Active Directory / LDAP Authentication -layout: doc.nosidebar ---- -# AD and LDAP Authentication - -Connecting with __Active Directory__ or any other __LDAP__ server with Auth0 usually involves deploying and running a piece of software on your side. This guide will walk you through the steps to download, run, deploy and customize such component. - -> Note: to make the installation easier, this tutorial will provide real time feedback every time you complete a step. You should see a green check besides the headings for every step accomplished. This tutorial is intended to be used by a developer. - -##Prerequisites - -In order to install the __Auth0 AD/LDAP Connector__ you need to first install node.js. - -
- -
- -Once node.js has been installed, download and unzip the source code for the __Auth0 AD/LDAP Connector__: - -
- -
- -##1. Run the connector - -> NOTE: If you are running on Mac or Linux, before moving forward, delete the `node_modules` folder and reinstall the modules with this command `rm -rf node_modules/passport-windowsauth && npm install` - -Open a shell console, access the uncompressed folder and execute this command: - - > node server.js - -When prompted for the ticket url, paste the following: - - https://@@account.namespace@@/p/custom/@@ticket@@ - -> After entering the ticket, the connector will exchange trust information (like URLs, endpoints, etc.) with the server to complete the setup on Auth0. - -##2. Let's try to login! - -Now that you have a running authentication server, let's try to login with a test user. - - Test Login - -- Test User: __test__ -- Test Password: __123__ - -> By default, the connector will only allow one user to login: a __test__ user that is fixed in code. This is so you can try that everything works fine before changing it to use LDAP / AD. - -##3. Connecting to AD / LDAP - -To connect to AD or any LDAP directory: - -###Open the ```config.json``` file and edit the following variables: - -- `LDAP_URL`: the url for the ldap server (e.g.: `ldap://myldap.com:389`) -- `LDAP_BASE`: the base to query (e.g.: `DC=mycompany,DC=com`) -- `LDAP_BIND_USER`: a service account used for authentication and quering (currently this accepts the fully qualified name for the user, like CN=Foo,CN=Users,DC=fabrikam,DC=com) -- `LDAP_BIND_PASSWORD`: the password of the service account - -###Restart the server (`CTRL+C` to stop it and then `node server.js` again). Login again. - - Test Login - -**Congratulations!** If you get a green check on this step, it means the configuration of the connector is successfully completed. - ----- - -## Next Steps - -Read the following sections to learn how to customize the login page and how to deploy it. - -### Active Directory and Windows Integrated Authentication - -By default, the connector will use forms-based authentication to your LDAP directory. However, if you are deploying this on your network and you use Active Directory, it is possible to configure Windows Integrated Authentication, so the user who is joined to the AD domain does not have to enter credentials at all. This only works if the connector is deployed to a Windows based machine. - -To configure Windows Authentication: - -* Install iisnode [x86](https://github.com/downloads/WindowsAzure/iisnode/iisnode-full-iis7-v0.2.2-x86.msi) | [x64](https://github.com/downloads/WindowsAzure/iisnode/iisnode-full-iis7-v0.2.2-x64.msi) and the [rewrite module](http://www.iis.net/download/URLRewrite) -* Create a WebSite pointing to the folder running the site, disable Anonymous Authentication and enable __Windows Authentication__ only. -* Update the `config.json` file with the new `SERVER_URL` -* Everytime you change something in the config.json file, open a browser to /test-iss to test the configuration - - Test login again! - -### Customize the login page - -The login page can be customized by editing the [views/login.ejs](https://github.com/auth0/custom-connector/blob/master/views/login.ejs) file. - -### Deploy it - -The connector can be deployed on Windows and Linux. - -Once you have the final URL of the service (not localhost), update the `SERVER_URL` configuration setting localted in the `config.json` file with the new address and restart the server. - -#### Deployment options - -* [Windows (IIS / IISNode)](https://github.com/tjanczuk/iisnode) -* [Linux](http://howtonode.org/deploying-node-upstart-monit) - -### Production considerations - -To avoid man in the middle attacks, we strongly recommend that you configure the server to use TLS/SSL. If you are running under IIS, configure the [web site to use SSL](http://www.iis.net/learn/manage/configuring-security/how-to-set-up-ssl-on-iis). If you are hosting on Linux, change the [server.js](https://github.com/auth0/ad-ldap-connector/blob/master/server.js) to use an [https server in node.js](http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener). - -Finally, if you are looking for a highly available setup, you can simply install multiple instances of this server behind a Network Load Balancer. - -#### Troubleshooting - -**Error binding to LDAP** - -If you see this error in the console: - - Error binding to LDAP dn: - code: 49 - message: 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db1 - -It means that the service account is not valid (this is the account that is used to connect to LDAP, not the end user account). Try using the distinguished name of the user on the `LDAP_USER_BIND` property (e.g.: `CN=John Foo,CN=Users,DC=fabrikam,DC=com`). - - - - diff --git a/docs/amazon-clientid.md b/docs/amazon-clientid.md index a300615a..8083be46 100644 --- a/docs/amazon-clientid.md +++ b/docs/amazon-clientid.md @@ -5,7 +5,7 @@ To configure an Amazon connection you will need to register Auth0 on the Amazon ##1. Add a new Application Log in into [Amazon](http://login.amazon.com) and select __App Console__: -![](img/amazon-login-1.png) +![](//cdn.auth0.com/docs/img/amazon-login-1.png) --- @@ -13,7 +13,7 @@ Log in into [Amazon](http://login.amazon.com) and select __App Console__: Click on the __Register New Application__ button and complete the form: -![](img/amazon-register-app.png) +![](//cdn.auth0.com/docs/img/amazon-register-app.png) The callback address for your app should be: @@ -25,6 +25,6 @@ The callback address for your app should be: Once the application is registered, enter your new `ClientId` and `ClientSecret` into the connection settings in Auth0. -![](img/amazon-add-connection.png) +![](//cdn.auth0.com/docs/img/amazon-add-connection.png) diff --git a/docs/android-tutorial.md b/docs/android-tutorial.md index ea188e37..7b98a426 100644 --- a/docs/android-tutorial.md +++ b/docs/android-tutorial.md @@ -1,6 +1,6 @@ # Using Auth0 with Android -Integrating Auth0 with Android based apps relies on the common method of instantiating a [WebView](http://developer.android.com/reference/android/webkit/WebView.html) inside the native app to drive all interactions with the authentication providers, and then extracting security tokens once they become available. +Integrating Auth0 with Android based apps relies on the common method of instantiating a [WebView](http://developer.android.com/reference/android/webkit/WebView.html) inside the native app to drive all interactions with the authentication providers, and then extracting security tokens once they become available. ##Before you start @@ -21,7 +21,7 @@ static final String Connection = "google-oauth2"; //change to "paypal", "linked The sample shows two buttons for Login. The one labeled __Login__, will initiate the login process with the connection specified in the constant `Connection` (google-oauth2 in this sample). The button labeled __Login With Widget__ will display the __Auth0 Login Widget__ and allow you to choose any configured Identity Provider. -![](img/android.png) +![](//cdn.auth0.com/docs/img/android.png) ##Using the library @@ -57,16 +57,16 @@ protected void onActivityResult(int requestCode, int resultCode, Intent authActi // result have two properties `accessToken` and `JsonWebToken`. You can use the `accessToken` to call the Auth0 API and retrieve the profile of the user that just logged in // the `JsonWebToken` is a signed JSON that can be sent to your APIs - + String userInfoUrl = String.format("https://@@account.namespace@@/userinfo?access_token=%s", result.accessToken); new AsyncTask() { @Override protected JSONObject doInBackground(String... url) { JSONObject json = RestJsonClient.connect(url[0]); - + return json; } - + @Override protected void onPostExecute(JSONObject user) { try { @@ -84,8 +84,6 @@ protected void onActivityResult(int requestCode, int resultCode, Intent authActi Congratulations! -> If you want to connect with a __Windows Azure Mobile Services__ (WAMS) backend, you should create a Windows Azure Mobile application on Auth0 (New App button). When you do that, Auth0 will sign the JsonWebToken with WAMS' `masterkey`, and you will be able to call their endpoints. Notice, that you can delete the Android app on Auth0 and use the WAMS instead and change the `client_id` parameter. - Are you an Android ninja? > __HEADS UP__ Help us to build an awesome [Android SDK](https://github.com/auth0/Auth0-Android) and get a free license! Checkout the issues in the GitHub project and provide feeback and/or pull requests. diff --git a/docs/angular-tutorial.md b/docs/angular-tutorial.md new file mode 100644 index 00000000..782616d2 --- /dev/null +++ b/docs/angular-tutorial.md @@ -0,0 +1,200 @@ +# AngularJS Tutorial + +> Note: If you're creating a new AngularJS app that you'll use with your API, you can [download a seed project](https://github.com/auth0/auth0-angular-api-sample/archive/gh-pages.zip) that is already configured to use Auth0. You only have to change the `authProvider` configuration to use your Auth0's account as shown in Step 2. + +If you already have an existing application, please follow the steps below. + +### 1. Setting up the callback URL in Auth0 + +
+

After authenticating the user on Auth0, we will do a GET to a URL on your web site. For security purposes, you have to register this URL on the Application Settings section on Auth0 Admin app.

+ +
http://localhost:PORT/
+
+ +### 2. Adding the Auth0 scripts and setting the right viewport + +```html + + + + + + + + + +``` + +We're including Auth0's angular module and its dependencies to the `index.html`. + +### 3. Add the module dependency and configure the service + +Add the `auth0` module dependency to your angular app definition and configure it by calling the `init` method of the `authProvider` + +```js +// app.js +angular.module('sample', ['auth']) + .config(function(authProvider) { + authProvider.init({ + clientID: '@@account.clientId@@', + callbackURL: location.href, + domain: '@@account.namespace@@' + }) + }); +``` + +> Note: `clientID` and `domain` are pre-populated with the right values if you are signed in to your Auth0 account. + +### 4. Triggering the login + +Now we're ready to implement the Login. We can inject the `auth` service in any controller and just call `signin` method to show the Login / SignUp popup. In this case, we'll add the call in the login method of the controller. The `signin` method returns a promise. That means that we can handle login success and failure the following way: + +```js +// LoginCtrl.js +$scope.login = function() { + auth.signin({ + popup: true + }) + .then(function() { + // Success callback + }, function() { + // Error callback + }); +} +``` + +```html + + + + +``` + +#### Login Widget Previewer + +@@browser@@ + +> Note: there are multiple ways of implementing login. What you see above is the Login Widget, but if you want to have your own UI you can change the ` + +``` + +## Basic operations + +### Login + +Trigger the login on any of your enabled `Connections` with: + +``` + //trigger login with google + $('.login-google').click(function () { + auth0.login({ + connection: 'google-oauth2' + }); + }); + + //trigger login with github + $('.login-github').click(function () { + auth0.login({ + connection: 'github' + }); + }); + + //trigger login with an enterprise connection + $('.login-github').click(function () { + auth0.login({ + connection: 'contoso.com' + }); + }); + + //trigger login with a db connection that requires username/password + $('.login-dbconn').click(function () { + auth0.login({ + connection: 'db-conn', + username: $('.username').val(), + password: $('.password').val(), + }); + }); + + //trigger login with a db connection and avoid the redirect (best experience for SPA) + $('.login-dbconn').click(function () { + auth0.login({ + connection: 'db-conn', + username: $('.username').val(), + password: $('.password').val(), + }, + function (err, profile, id_token, access_token) { + // store in cookies + }); + }); + + //trigger login popup with google + $('.login-google-popup').click(function (e) { + e.preventDefault(); + auth0.login({ + connection: 'google-oauth2', + popup: true, + popupOptions: { + width: 450, + height: 800 + } + }, function(err, profile, id_token, access_token, state) { + if (err) { + alert("something went wrong: " + err.message); + return; + } + alert('hello ' + profile.name); + }); + }); +``` + +You can also request `scopes` that are not pre-configured for your social connections: + +``` + //trigger login requesting additional scopes with google + $('.login-google').click(function () { + auth0.login({ + connection: 'google-oauth2', + connection_scope: ['https://www.googleapis.com/auth/orkut', 'https://picasaweb.google.com/data/'] + }); + }); + + // alternatively a comma separated list also works + $('.login-google').click(function () { + auth0.login({ + connection: 'google-oauth2', + connection_scope: 'https://www.googleapis.com/auth/orkut,https://picasaweb.google.com/data/' + }); + }); +``` + +Trigger the login with offline mode support to get the `refresh_token` + +``` +$('.login-dbconn').click(function () { + auth0.login({ + connection: 'db-conn', + username: $('.username').val(), + password: $('.password').val(), + scope: 'openid offline_access' + }, + function (err, profile, id_token, access_token, state, refresh_token) { + // store in cookies + // refresh_token is sent because offline_access is set as a scope + }); + }); +``` + +### Processing the callback + +#### Redirect Mode + +Once you have succesfully authenticated, Auth0 will redirect to the `callbackURL` parameter defined in the constructor. Auth0 will append a few extra parameters after a hash on the URL. These include: an `access_token` and an `id_token` (a JWT). You can parse the hash and retrieve the full user profile as follows: + +``` + $(function () { + var result = auth0.parseHash(window.location.hash); + + //use result.id_token to call your rest api + + if (result && result.id_token) { + auth0.getProfile(result.id_token, function (err, profile) { + alert('hello ' + profile.name); + }); + // If offline_access was a requested scope + // You can grab the result.refresh_token here + + } else if (result && result.error) { + alert('error: ' + result.error); + } + }); +``` + +Or just parse the hash (if loginOption.scope is not `openid profile`, then the profile will only contains the `user_id`): + +```js + $(function () { + var result = auth0.parseHash(window.location.hash); + if (result && result.profile) { + alert('your user_id is: ' + result.profile.sub); + //use result.id_token to call your rest api + } + }); + }); +``` + +If there is no hash, `result` will be null. If the hash contains an `id_token`, the profile field will be populated. + +#### Popup Mode + +While using this mode, the result will be passed as the `login` method callback; + +```js + auth0.login({ popup: true }, function(err, profile, id_token, access_token, state, refresh_token) { + if (err) { + // Handle the error! + return; + } + + //use id_token to call your rest api + alert('hello ' + profile.name); + + // refresh_token is sent only if offline_access is set as a scope + }); +}); +``` + +## Operations for Database connections + +### Sign-ups + +If you use [Database Connections](https://docs.auth0.com/mysql-connection-tutorial) you can signup new users with: + +``` + $('.signup').click(function () { + auth0.signup({ + connection: 'db-conn', + username: 'foo@bar.com', + password: 'shhh...secret' + }, function (err) { + console.log(err.message); + }); + }); +``` + +After a successful login, `auth0.js` will auto login the user. If you do not want this to happen, use the `auto_login: false` option in the signup method. + +### Change Password + +``` + $('.change_password').click(function () { + auth0.changePassword({ + connection: 'db-conn', + username: 'foo@bar.com', + password: 'bla bla instead of shhh...secret' // the new password + }, function (err, resp) { + console.log(err.message); + }); + }); +``` + +##Advanced operations + +###Delegation Token Request + +A delegation token is a new token for a different service or app/API. + +If you just want to get a new token for an application registered in Auth0 that is not the current one, you can do the following: + +``` +var options = { + id_token: 'your-id-token', // The id_token you have now + targetClientId: 'The-ClientId-Of-The-App-you-are-getting-a-JWT-for' + api: 'firebase', // The type of app (Auth0 can generate multiple token formats) + scope: "openid profile" // default: openid +}; + +auth0.getDelegationToken(options, function (err, delegationResult) { + // Call your API using delegationResult.id_token +}); +``` + +### Refresh tokens + +If you want to refresh your existing (non-expired) token: + +``` +auth0.renewIdToken(current_id_token, function (err, delegationResult) { + // Get here the new delegationResult.id_token +}); +``` + +If you want to refresh your existing (expired) token, if you have the refresh_token, you can call the following: + +``` +auth0.refreshToken(refresh_token, function (err, delegationResult) { + // Get here the new delegationResult.id_token +}); +``` + +### Validate a User + +You can validate a user of a specific (db) connection with his username and password: + +``` +auth0.validateUser({ + connection: 'db-conn', + username: 'foo@bar.com', + password: 'blabla' +}, function (err, valid) { }); +``` + +### SSO + +The `getSSOData` method fetches Single-Sign-On information from Auth0: + +``` + auth0.getSSOData(function (err, ssoData) { + if (err) return console.log(err.message); + expect(ssoData.sso).to.exist; + }); +``` + +``` + // Don't bring active directory data + auth0.getSSOData(false, fn); +``` + +## Issue Reporting + +If you have found a bug or if you have a feature request, please report them as [issues in our repository](https://github.com/auth0/auth0.js/issues). Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. \ No newline at end of file diff --git a/docs/aws-api-setup.md b/docs/aws-api-setup.md new file mode 100644 index 00000000..c2f2534a --- /dev/null +++ b/docs/aws-api-setup.md @@ -0,0 +1,66 @@ +# How to Setup AWS to do Delegated Authentication with APIs + +In order to do delegated authentication with AWS APIs you have to create a **SAML Provider** and one or more **Roles**. This document explains how to do that. + +## Creating the SAML Provider + +This is a one time setup + +1. Go to the **AWS IAM Console** and click on **Identity Providers** + + ![](//cdn.auth0.com/docs/img/aws-api-setup-1.png) + +2. Click on **Create SAML Provider** and give it a name. + + ![](//cdn.auth0.com/docs/img/aws-api-setup-2.png) + +3. Download the metadata from https://@@account.namespace@@/samlp/metadata/@@account.clientId@@ and upload it here. + + ![](//cdn.auth0.com/docs/img/aws-api-setup-3.png) + +4. The SAML Provider was created. You can close the modal. + + ![](//cdn.auth0.com/docs/img/aws-api-setup-4.png) + +## Creating Roles + +Now, you have to create a role that will have one or more policies associated. You can create as many roles as you want. + +1. Go to **Roles**. + + ![](//cdn.auth0.com/docs/img/aws-api-setup-5.png) + +2. Click on **Create New Role** and give it an arbitrary name. + + ![](//cdn.auth0.com/docs/img/aws-api-setup-6.png) + +3. Select or enter the following values. + + * **SAML Provider**: the provider you've just created + * **Attribute**: `SAML:iss` + * **Value**: `urn:@@account.namespace@@` + + + ![](//cdn.auth0.com/docs/img/aws-api-setup-7.png) + +4. This is just a confirmation. Click on **Continue**. + + ![](//cdn.auth0.com/docs/img/aws-api-setup-8.png) + +5. This is where you assign permissions to this role. In this example, we are creating a policy that give full access to the S3 resource `YOUR_BUCKET/${saml:sub}`. This will be evaluated on runtime and replaced with the `user_id` of the logged in user. + + ![](//cdn.auth0.com/docs/img/aws-api-setup-9.png) + + > For more information about what AWS APIs are supported: + +6. Confirm the creation of the new role. + + ![](//cdn.auth0.com/docs/img/aws-api-setup-10.png) + +7. Copy the **Role ARN** value. You will use this value later when calling the `/delegation` endpoint in Auth0. + + ![](//cdn.auth0.com/docs/img/aws-api-setup-11.png) + +8. Copy the **Principal ARN** value. You will use this value later when calling the `/delegation` endpoint in Auth0. + + ![](//cdn.auth0.com/docs/img/aws-api-setup-12.png) diff --git a/docs/aws.md b/docs/aws.md new file mode 100644 index 00000000..61786a0e --- /dev/null +++ b/docs/aws.md @@ -0,0 +1,180 @@ +# AWS Integration in Auth0 + +Auth0 ships with AWS IAM integration out of the box that allows you to: + +1. Login to AWS Dashboard with any of the [supported Identity Providers](identityproviders). +2. Obtain AWS Tokens to securely call AWS APIs and resources. + +###SSO with AWS Dashboard + +It's straight forward to configure Auth0 for federation with AWS using SAML. + +1. Add a new App in Auth0 and enable the __SAML2 Web App__ add-on. +2. Use the `https://signin.aws.amazon.com/saml` for the __Application Callback URL__ +3. Use this default __SAML configuration__: + +``` +{ + "audience": "https://signin.aws.amazon.com/saml", + "mappings": { + "email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + "name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", + }, + "createUpnClaim": false, + "passthroughClaimsWithNoMapping": false, + "mapUnknownClaimsAsIs": false, + "mapIdentities": false, + "nameIdentifierFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", + "nameIdentifierProbes": [ + "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + ], +} +``` + +4. Configure Auth0 as the IdP for AWS. AWS will require importing the __IdP Metadata__. Scroll down on the same configuration screen on the Auth0 dashboard. Look for the __Identity Provider Metadata__ link. + +5. Send __AWS roles__ or write a Rule to map values to it, like this example: + +``` +function (user, context, callback) { + + user.awsRole = 'arn:aws:iam::951887872838:role/TestSAML,arn:aws:iam::951887872838:saml-provider/MyAuth0'; + user.awsRoleSession = 'eugeniop'; + + context.samlConfiguration.mappings = { + "https://aws.amazon.com/SAML/Attributes/Role": "awsRole", + "https://aws.amazon.com/SAML/Attributes/RoleSessionName": "awsRoleSession" + }; + callback(null, user, context); +} +``` + +Notice that how you obtain these 2 values in multiple ways. The above, just hardcodes them. You could store these in the __User Profile__, or you could also derive them from other attributes. For example, you might use Active Directory and have properties already associated with users (e.g. `groups`). You can then define a map between `groups` and `AWS roles`: + +``` +... +var awsRoles = { + 'DomainUser': 'arn:aws:iam::951887872838:role/TestSAML,arn:aws:iam::95123456838:saml-provider/MyAuth0', + 'DomainAdmins': arn:aws:iam::957483571234:role/SysAdmins,arn:aws:iam::95123456838:saml-provider/MyAuth0' +}; + +context.samlConfiguration.mappings = { + "https://aws.amazon.com/SAML/Attributes/Role": awsRoles[user.group], + "https://aws.amazon.com/SAML/Attributes/RoleSessionName": user.name, + +}; +... +``` + +The __AWS roles__ you send will be associated to an __AWS IAM Policy__ that will enforce the type of access allowed for a resource, including the AWS dashboard. Notice the __AWS Role__ has structure of `{Fully qualified Role name},{Fully qualified identity provider}`. In the sample above the IdP is identified as `arn:aws:iam::951887872838:saml-provider/MyAuth0`. + +More information on roles, policies see [here](http://docs.aws.amazon.com/IAM/latest/UserGuide/roles-creatingrole.html). + +--- + +###Delegation Scenarios + +This second scenario is even more powerful. Auth0 can interact with __AWS STS__ directly, and obtain an __AWS token__ that can be used to call any AWS API. + +This works with any supported [Identity Provider](identityproviders) in Auth0: + + + +In the example above, a web application authenticates users with social providers (e.g. GitHub, LinkedIn, Facebook, Twitter) or with corporate credentials (e.g. Active Directory, Office365 and Salesforce) in step 1. + +It then calls the __Identity delegation__ endpoint in Auth0 (step 2), and requests an AWS Token. Auth0 obtains the token from AWS STS (step 3). + +The app can then use the AWS Token to connect with S3 or EC2 or any AWS API. + +As an example of an IAM policy: + + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "s3:DelObject", + "s3:GetObject", + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Resource": [ + "arn:aws:s3:::YOUR_BUCKET_NAME/${saml:sub}/*" + ], + "Effect": "Allow" + } + ] + } + +This is a *dynamic* policy that gives access to a folder in a bucket. The folder name will be set based on an attribute of a SAML token digitally signed that Auth0 exchanges with AWS on your behalf (step 3). + +The `${saml:sub}` will be automagically mapped from the authenticated user (`sub` means `subject`, that will equal to the user identifier). This means that the __original__ identity of the user can be used throughout the system: your app, S3, etc. + + +###Getting the AWS Token for an authenticated user + +When a user authenticates with Auth0 you will get back an `id_token` (a [JWT](jwt)). You would then use this `id_token` to request Auth0 and AWS Token using the delegation endpoint: + +This is a sample Request on the delegation endpoint + + POST https://@@account.namespace@@/delegation + + grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer + &id_token=THE_ID_TOKEN_OF_LOGGED_IN_USER + &target=CLIENT_ID_OF_API_TO_CALL + &client_id=THE_CLIENT_ID_OF_CALLER + &role=arn:aws:iam::010616021751:role/foo + &principal=arn:aws:iam::010616021751:saml-provider/idpname + +Notice the 2 additional parameters used for AWS: + + &role=arn:aws:iam::010616021751:role/foo + &principal=arn:aws:iam::010616021751:saml-provider/idpname + +The Response will contain the AWS Token: + +``` +{ + Credentials: { + SessionToken: 'AQoDYXdzENf//////...Pz02lt4FSCY6L+WBQ==', + SecretAccessKey: 'zYaN30nMf/9uV....Zx9Em7xQzcCc9/PPl', + Expiration: Fri Jan 10 2014 11:22:32 GMT-0300 (ART), + AccessKeyId: 'ASIAI5PCTTOC6APKKXLQ' + } +} +``` + +> The Auth0 client libraries simplify calling these endpoint. Check [our GitHub repo](https://github.com/auth0/) for the latest SDKs. Here's [one for client side JavaScript](https://github.com/auth0/auth0.js#delegation-token-request). + +###Client Side sample code + +``` + var targetClientId = "{TARGET_CLIENT_ID}"; + + var options = { + "id_token": "USER_ID_TOKEN", // MANDATORY! + "client_id": "THE_CLIENT_ID_OF_CALLER", + "role": "arn:aws:iam::010616021751:role/foo", + "principal": "arn:aws:iam::010616021751:saml-provider/idpname" + }; + + auth0.getDelegationToken(targetClientId, options, function (err, delegationResult) { + uploadFileToS3(delegationResult.Credentials, done); + }); + + function uploadFileToS3 (awsToken, callback) { + $('#upload').on('click', function() { + var params = { + Key: user.id + '/' + file.name, + ContentType: fileChooser.files[0].type, + Body: fileChooser.files[0]}; + + var bucket = new AWS.S3({params: {Bucket: 'THE_BUCKET'}}); + bucket.config.credentials = + new AWS.Credentials(awsToken.AccessKeyId, + awsToken.SecretAccessKey, + awsToken.SessionToken); + bucket.putObject(params, callback); + }); + } +``` diff --git a/docs/azure-tutorial.md b/docs/azure-tutorial.md index 810cce7a..504c4b85 100644 --- a/docs/azure-tutorial.md +++ b/docs/azure-tutorial.md @@ -1,8 +1,8 @@ -# Using Auth0 with Windows Azure +# Using Auth0 with Microsoft Azure -From an Auth0 integration perspective, the code is the same, regardless you are running on Windows Azure or your local dev environment. +From an Auth0 integration perspective, the code is the same, regardless of where your app is running: on Microsoft Azure or your local dev environment. -To integrate applications supported by the Windows Azure platform, consider these tutorials: +To integrate applications supported by the Microsoft Azure platform, consider these tutorials: * [ASP.NET application](aspnet-tutorial)
Simple non-intrusive integration with any version of ASP.NET. @@ -10,18 +10,16 @@ Simple non-intrusive integration with any version of ASP.NET. * [Node.js application](nodejs-tutorial)
Integration using [passport](http://passportjs.org/). -* [Windows Azure Mobile Services](http://blog.auth0.com/2013/03/17/Authenticate-Azure-Mobile-Services-apps-with-Everything-using-Auth0/)
-Blog post explaining how to integrate with a Windows Azure Mobile Services backend. +* [Microsoft Azure Mobile Services](http://blog.auth0.com/2013/03/17/Authenticate-Azure-Mobile-Services-apps-with-Everything-using-Auth0/)
+Blog post explaining how to integrate with a Microsoft Azure Mobile Services backend. --- -### Tip: change Auth0 configuration when deploying to Windows Azure +### Tip: change Auth0 configuration when deploying to Microsoft Azure -There is one consideration that you might want to take into account when deploying to Windows Azure (or any other environment). +There is one consideration that you might want to take into account when deploying to Microsoft Azure (or any other environment). -We recommend creating one application per environment in Auth0. - -![](img/environments.png) +We recommend creating one application per environment in Auth0 (e.g. "Dev", "Test", "QA", etc). Each application has a different `Client Id` and `Client Secret` and can be configured with a different callback URL. You can use the [Web.config transformations](http://msdn.microsoft.com/en-us/library/dd465326.aspx) to apply a transformation depening on the Build Configuration you use. For instance @@ -46,10 +44,17 @@ Each application has a different `Client Id` and `Client Secret` and can be conf Then, whenever you have to reference the ClientID, Secret or callback, you use this syntax: ``` - + + ``` -Whether you deploy to Windows Azure Web Sites or a Cloud Service the Web.config transformation will run. +Whether you deploy to Microsoft Azure Web Sites or a Cloud Service the Web.config transformation will run. > **TIP**: to test your web.config transforms you can use this [awesome tool](http://webconfigtransformationtester.apphb.com/) diff --git a/docs/baidu-clientid.md b/docs/baidu-clientid.md new file mode 100644 index 00000000..cbe8d798 --- /dev/null +++ b/docs/baidu-clientid.md @@ -0,0 +1,28 @@ +# Obtaining an API Key and Secret Key for Baidu + +To configure a Baidu OAuth2 connection you will need to register your Auth0 tenant on their [integration portal](https://developer.baidu.com/dev). + +##1. Log in into the integration portal and register a new App: + +![](//cdn.auth0.com/docs/img/baidu-register-1.png) + +--- + +##2. Get your API Key and Secret Key + +Once the application is registered, enter your new `API Key` and `Secret Key` into the connection settings in Auth0. + +![](//cdn.auth0.com/docs/img/baidu-register-2.png) + +--- + +##3. Enter the callback URL: + +Use the following value for the callback URL: + + https://@@account.namespace@@/login/callback + +Select your application on the console, and then click on `API 管理 -> 安全设置` + +![](//cdn.auth0.com/docs/img/baidu-register-3.png) + diff --git a/docs/box-clientid.md b/docs/box-clientid.md index ffc1d650..8b87d23d 100644 --- a/docs/box-clientid.md +++ b/docs/box-clientid.md @@ -4,9 +4,9 @@ To configure a Box OAuth2 connection you will need to register your Auth0 tenant ##1. Log in into the developer portal and register a new App: -![](img/box-register-1.png) +![](//cdn.auth0.com/docs/img/box-register-1.png) -![](img/box-register-2.png) +![](//cdn.auth0.com/docs/img/box-register-2.png) --- @@ -14,11 +14,11 @@ To configure a Box OAuth2 connection you will need to register your Auth0 tenant After the app is created, click on __Edit__ and review the (long) form. There're a number of properties that you can change (e.g. contact information, logos, etc.). -![](img/box-register-3.png) +![](//cdn.auth0.com/docs/img/box-register-3.png) -Scroll down and you'll find the `client_id` and `client_secret` fields under the __OAuth2 Parameters__ section: +Scroll down and you'll find the `client_id` and `client_secret` fields under the __OAuth2 Parameters__ section: -![](img/box-register-4.png) +![](//cdn.auth0.com/docs/img/box-register-4.png) Enter this URL as the `redirect_uri`: diff --git a/docs/bulk-import.md b/docs/bulk-import.md new file mode 100644 index 00000000..cc09678d --- /dev/null +++ b/docs/bulk-import.md @@ -0,0 +1,199 @@ +# Massively importing users to Auth0 using a job + +Our focus has always been not only greenfield projects but also existing applications that want to extend their Authentication capabilities. + +With this in mind, our [API has an endpoint](https://login.auth0.com/docs/api/v2/#!/users-imports/post_users_imports) that allows consumers to populate a database connection with users obtained from a file. Each of those users will have to reset their password on they log in for the first time. + +## Pre-requisites + +Before you launch the import users job, a database to where users will be imported must exist. + +### Users schema +The user's file must have an array with the user's information in JSON format. The following [JSON schema](http://json-schema.org) describes valid users: +```json +{ + "type": "object", + "properties": { + "email_verified": { + "type": "boolean" + }, + "email": { + "type": "string", + "description": "The email of the user.", + "format": "email" + }, + "username": { + "type": "string", + "description": "The username." + }, + "family_name": { + "type": "string", + "description": "The user's last name." + }, + "gender": { + "type": "string", + "description": "The user's gender." + }, + "name": { + "type": "string", + "description": "The user's full name." + }, + "nickname": { + "type": "string", + "description": "The user's nickname." + }, + "picture": { + "type": "string", + "description": "The URI from where to obtain the user's picture." + }, + "locale": { + "type": "string", + "description": "The user's locale." + }, + "given_name": { + "type": "string", + "description": "The user's first name." + }, + "loginsCount": { + "type": "number", + "description": "The amout of times the user has logged in.", + "minimum": 1, + "maximum": 1 + }, + "metadata": { + "type": "object", + "description": "Additional properties related to the user." + }, + "created_at": { + "type": "object", + "description": "The date when the user was created." + }, + "identities": { + "type": "array", + "description": "The user's identities.", + "items": { + "type": "object", + "required": ["profileData", "provider", "connection", "isSocial"], + "properties": { + "profileData": { + "type": "object", + "required": ["email", "email_verified"], + "properties": { + "email_verified": { + "type": "boolean" + }, + "email": { + "type": "string", + "description": "The email of the user's identity.", + "format": "email" + } + } + }, + "provider": { + "type": "string", + "description": "The identity provider.", + "enum":["auth0"] + }, + "connection": { + "type": "string", + "description": "The connection of the user's identity." + }, + "isSocial": { + "type": "boolean", + "description": "True if the IdP is social, false otherwise.", + "enum":[false] + } + } + }, + "minItems": 1, + "maxItems": 1 + } + }, + "required": ["email", "email_verified", "identities", "loginsCount", "created_at"], + "id": "user", + "additionalProperties": false +} +``` + +### User metadata schema +Additionally, the metadata property must be valid for the following schema: +```json +{ + "type": "object", + "properties": { + "clientID": { "not": {} }, + "globalClientID": { "not": {} }, + "global_client_id": { "not": {} }, + "email_verified": { "not": {} }, + "user_id": { "not": {} }, + "identities": { "not": {} }, + "lastIP": { "not": {} }, + "lastLogin": { "not": {} }, + "metadata": { "not": {} }, + "created_at": { "not": {} }, + "loginsCount": { "not": {} }, + "_id": { "not": {} } + }, + "id": "user_metadata" +} +``` + +Basically, it should not contain any of the properties listed above. + +### File example +A file with the following contents is valid: +```json +[ + { + "email": "john.doe@contoso.com", + "family_name": "Doe", + "email_verified": false, + "gender": "male", + "name": "John Doe", + "nickname": "Johnny", + "given_name": "John" + } +] +``` + +## How does it work? +When you perform a request to the endpoint you will get a response similar to the following one: +``` +Code: 202. +Body +{ + "status":"pending", + "type":"users_import", + "tenant":"contoso", + "connection":"abcd", + "_id":"545bb6aca4109a44b38ba231" +} +``` + +The returned entity represents the import job. You can query its status using [this other endpoint](https://login.auth0.com/docs/api/v2/#!/users-imports/get_users_imports_by_job_id). + +Once the job finishes, whether it failed or was successful, Auth0 account owners will get an e-mail notifying about the result. + +For example, one possible failure notification could be: +``` +Subject +----- +[Auth0] Import user job for tenant contoso and connection abcd failed + +Body +--- +Failed to parse users file JSON when importing users. Make sure it is valid JSON. +``` + +If the job was succesful owners would get an e-mail like this one: +``` +Subject +----- +[Auth0] Import user job for tenant contoso and connection abcd completed + +Body +---- +New users: 1 +Total users: 15 +Duplicate Users: 0 +``` diff --git a/docs/checksum.md b/docs/checksum.md new file mode 100644 index 00000000..824620fa --- /dev/null +++ b/docs/checksum.md @@ -0,0 +1,29 @@ +# Checking the shasum of a package + +To prevent transmission or storage corruption every download file is provided with a SHA-1 checksum. + +To check the integrity of the file, use the following command: + +### Linux and OS X + +``` +shasum file +``` + +The output of this command will be the shasum of the downloaded package. You should compare the number displayed by the shasum command with the shasum provided in the instructions. + +### Windows + +A shasum tool for Windows can be obtained from: + +[http://www.microsoft.com/en-us/download/details.aspx?id=11533](http://www.microsoft.com/en-us/download/details.aspx?id=11533) + +Usage: + +``` +fciv.exe -sha1 file +``` + +where xxxx is the build number. + +You must include the `-sha1` option since __fciv.exe__ uses md5 by default. More information [here](http://support2.microsoft.com/kb/889768). \ No newline at end of file diff --git a/docs/client-platforms/angularjs.md b/docs/client-platforms/angularjs.md new file mode 100644 index 00000000..922d8c8b --- /dev/null +++ b/docs/client-platforms/angularjs.md @@ -0,0 +1,215 @@ +--- +lodash: true +--- + +## AngularJS Tutorial + + + +**Otherwise, if you already have an existing application, please follow the steps below.** + +@@includes.callback@@ + +### 1. Adding the Auth0 scripts and setting the right viewport + +````html + + + + + + + + + + + + + +``` + +We're including Auth0's angular module and its dependencies to the `index.html`. + +### 2. Add the module dependency and configure the service + +Add the `auth0`, `angular-storage` and `angular-jwt` module dependencies to your angular app definition and configure `auth0` by calling the `init` method of the `authProvider` + +````js +// app.js +angular.module('YOUR-APP-NAME', ['auth0', 'angular-storage', 'angular-jwt']) +.config(function (authProvider) { + authProvider.init({ + domain: '<%= account.namespace %>', + clientID: '<%= account.clientId %>' + }); +}) +.run(function(auth) { + // This hooks al auth events to check everything as soon as the app starts + auth.hookEvents(); +}); +``` + + +### 3. Let's implement the login + +Now we're ready to implement the Login. We can inject the `auth` service in any controller and just call `signin` method to show the Login / SignUp popup. +In this case, we'll add the call in the `login` method of the `LoginCtrl` controller. On login success, we'll save the user profile and token into `localStorage`. + +````js +// LoginCtrl.js +function LoginCtrl(store, $location) { + $scope.login = function() { + auth.signin({}, function(profile, token) { + // Success callback + store.set('profile', profile); + store.set('token', token); + $location.path('/'); + }, function() { + // Error callback + }); + } +} +``` + +````html + + + + +``` + +@@browser@@ + +> Note: there are multiple ways of implementing login. What you see above is the Login Widget, but if you want to have your own UI you can change the ` + + + +``` + +We're including the Auth0 lock script to the `index.html` + +### 2. Configure the Auth0Lock + +Configuring the Auth0Lock will let your app work with Auth0: + +````js +var lock = null; +$(document).ready(function() { + lock = new Auth0Lock('@@account.clientId@@', '@@account.namespace@@'); +}); +``` + +### 3. Let's implement the login + +Now we're ready to implement the login. Once the user clicks on the login button, we'll call the `.show()` method of Auth0's `lock` we've just created. + +````js +var userProfile; + +$('.btn-login').click(function(e) { + e.preventDefault(); + lock.show(function(err, profile, token) { + if (err) { + // Error callback + alert('There was an error'); + } else { + // Success calback + + // Save the JWT token. + localStorage.setItem('userToken', token); + + // Save the profile + userProfile = profile; + } + }); +}); +``` + +````html + + + +``` + +We need to save the token so that we can use it later when calling a server or an API. In this case, we're saving that token in LocalStorage. + +If you want to check all the available arguments for the signin call, please [check here](@@base_url@@/lock#5) + +@@browser@@ + +<% if (configuration.api && configuration.thirdParty) { %> + +### 4. Configuring calls to a Third Party API + +Now, we want to be able to call <%= configuration.api %> which is a third party api. What we're going to do is to exchange the JWT token we got from Auth0 for a token we can use to query <%= configuration.api %> securely and authenticated. + +For that, we're going to modify the login call we did in step #4. We're going to add the call to get the new token + +````js +var userProfile; + +$('.btn-login').click(function(e) { + e.preventDefault(); + lock.show(function(err, profile, token) { + if (err) { + // Error callback + alert('There was an error'); + } else { + // Success calback + + + // Call to get new token starts here + + lock.getClient().getDelegationToken({ + id_token: token, + // By default the first active third party add-on will be used + // However, We can specify which third party API to use here by specifying the name of the add-on + // api: <%= configuration.api %> + }, + function(err, thirdPartyApiToken) { + localStorage.setItem('thirdPartyApiToken', thirdPartyApiToken.id_token); + }); + + // Call to get new token ends here + + // Save the JWT token. + localStorage.setItem('userToken', token); + + // Save the profile + userProfile = profile; + } + }}); +}); +``` + +We're going to activate the <%= configuration.api %> add-on in the following steps. Once we do that, the code we wrote here will just work. + +<% } else { %> + +### 4. Configuring secure calls to your API + +As we're going to call an API we're going to make <%= configuration.api ? ('on ' + configuration.api) : '' %>, we need to make sure we send the [JWT token](@@base_url@@/jwt) we receive on the login on every request. For that, we need to implement `$.ajaxSetup` so that every ajax call sends the `Authorization` header with the correct token. + +````js +$.ajaxSetup({ + 'beforeSend': function(xhr) { + if (localStorage.getItem('userToken')) { + xhr.setRequestHeader('Authorization', + 'Bearer ' + localStorage.getItem('userToken')); + } + } +}); +``` + +Please note that we're using the JWT that we saved after login on Step [#4](#5). + +<% } %> + +> The settings specified in `ajaxSetup` will affect all calls to $.ajax or Ajax-based derivatives such as $.get(). This can cause undesirable behavior since other callers (for example, plugins) may be expecting the normal default settings. For that reason is recommend against using this API. Instead, set the options explicitly in the call or define a simple plugin to do so ([more details](http://api.jquery.com/jQuery.ajaxSetup/)). + +### 5. Showing user information + +We already have the `userProfile` variable with the user information. Now, we can set that information to a span: + +````js +$('.nick').text(userProfile.nickname); +``` + +````html +

His name is

+``` + +You can [click here](@@base_url@@/user-profile) to find out all of the available properties from the user's profile. Please note that some of this depend on the social provider being used. + +### 6. Logging out + +In our case, logout means just deleting the saved token from localStorage and redirecting the user to the home page. + +````js +localStorage.removeItem('token'); +userProfile = null; +window.location.href = "/"; +``` + +### 7. You're done! + +You've implemented Login and Signup with Auth0 and jQuery. diff --git a/docs/client-platforms/socket-io.md b/docs/client-platforms/socket-io.md new file mode 100644 index 00000000..d664d0fe --- /dev/null +++ b/docs/client-platforms/socket-io.md @@ -0,0 +1,102 @@ +--- +lodash: true +--- + +## Socket.io Tutorial + +When using Realtime frameworks like Socket.io, Authentication is something extremely important. If you don't handle it correctly, a malicious user could hijack the stream and use it to get and send any information he'd want. + +In order to avoid that, you can configure Socket.io to work with JWT and particularly with Auth0. + +### Instructions + + + +Let's look at a simple sample that uses [express](http://expressjs.com/), [socket.io](http://socket.io) and handles authentication using Json Web Tokens (JWT). + +### Server Side + +Code speaks by itself. Focus on the `/login` and the usage of `socketioJwt.authorize`. + + var jwt = require('jsonwebtoken'); + // other requires + + app.post('/login', function (req, res) { + + // TODO: validate the actual user user + var profile = { + first_name: 'John', + last_name: 'Doe', + email: 'john@doe.com', + id: 123 + }; + + // we are sending the profile in the token + var token = jwt.sign(profile, jwtSecret, { expiresInMinutes: 60*5 }); + + res.json({token: token}); + }); + + var server = http.createServer(app); + +Then the socket.io server + + var socketioJwt = require('socketio-jwt'); + + var sio = socketIo.listen(server); + + sio.set('authorization', socketioJwt.authorize({ + secret: jwtSecret, + handshake: true + })); + + sio.sockets + .on('connection', function (socket) { + console.log(socket.handshake.decoded_token.email, 'connected'); + //socket.on('event'); + }); + + server.listen(9000, function () { + console.log('listening on http://localhost:9000'); + }); + +The JWT is signed with the `jwtSecret` which is stored only on the server. + +Here we are using the [global authorization callback](https://github.com/LearnBoost/socket.io/wiki/Authorizing) on socket.io. We are also using a simple module we wrote ([socketio-jwt](https://github.com/auth0/socketio-jwt)) to help us with the details of handling the JWT. This module expects the JWT in the querystring during the handshake. + +If the client sends a valid JWT, the handshake completes successfully and the `connection` event is triggered. + + +### Client Side + +A simple js client side code that uses this server is shown bellow: + + function connect_socket (token) { + var socket = io.connect('', { + query: 'token=' + token + }); + + socket.on('connect', function () { + console.log('authenticated'); + }).on('disconnect', function () { + console.log('disconnected'); + }); + } + + $('#login').submit(function (e) { + e.preventDefault(); + $.post('/login', { + username: $('username').val(), + password: $('password').val() + }).done(function (result) { + connect_socket(result.token); + }); + }); + +As stated before, this is much simpler than using cookies and sessions, and it is much easier to implement across different technologies. diff --git a/docs/client-platforms/vanillajs.md b/docs/client-platforms/vanillajs.md new file mode 100644 index 00000000..53e46c22 --- /dev/null +++ b/docs/client-platforms/vanillajs.md @@ -0,0 +1,99 @@ +--- +lodash: true +--- + +## Generic SPA / Vanilla JS Tutorial + +Please follow the steps below to configure your JS app to use Auth0. + +@@includes.callback@@ + +### 1. Adding the Auth0 scripts and setting the right viewport + +````html + + + + + +``` + +We're including the Auth0 lock script to the `index.html` + +### 2. Create the Auth0Lock instance + +Configuring the Auth0Lock will let your app work with Auth0 + +````js +var lock = null; +document.addEventListener( "DOMContentLoaded", function(){ + lock = new Auth0Lock('@@account.clientId@@', '@@account.namespace@@'); +}); +``` + +### 3. Let's implement the login + +Now we're ready to implement the Login. + +````html + + + +``` + +Once the user clicks on the login button, we'll call the `.show()` method of Auth0's `lock` we've just created. + +````js +var userProfile = null; + +document.getElementById('btn-login').addEventListener('click', function() { + lock.show({ popup: true }, function(err, profile, token) { + if (err) { + // Error callback + alert('There was an error'); + } else { + // Success calback + + // Save the JWT token. + localStorage.setItem('userToken', token); + + // Save the profile + userProfile = profile; + } + }); +}); +``` + +We need to save the token so that we can use it later when calling a server or an API. In this case, we're saving that token in LocalStorage. + +If you want to check all the available arguments for the show method, check the [Auth0Lock](@@base_url@@/lock) documentation. + + + +### 4. Showing user information + +We already have the `userProfile` variable with the user information. Now, we can set that information to a span: + +````js +document.getElementById('nick').textContent = userProfile.nickname; +``` + +````html +

His name is

+``` + +You can [click here](@@base_url@@/user-profile) to find out all of the available properties from the user's profile. Please note that some of this depend on the social provider being used. + +### 5. Logging out + +In our case, logout means just deleting the saved token from localStorage and redirecting the user to the home page. + +````js +localStorage.removeItem('userToken'); +userProfile = null; +window.location.href = "/"; +``` + +### 6. You're done! + +You've implemented Login and Signup with Auth0 and VanillaJS. diff --git a/docs/connector.md b/docs/connector.md new file mode 100644 index 00000000..cff584d5 --- /dev/null +++ b/docs/connector.md @@ -0,0 +1,15 @@ +## Connector + +Auth0 integrates with Active Directory/LDAP through the __Active Directory/LDAP Connector__ that you install in your network. + +In this section: + +- [Overview](@@env.BASE_URL@@/connector/overview) +- [Prerequisites](@@env.BASE_URL@@/connector/prerequisites) +- [Installing the connector on **Windows**](@@env.BASE_URL@@/connector/install) +- [Installing the connector on **other platforms**](@@env.BASE_URL@@/connector/install-other-platforms) +- [OpenLDAP and non-AD Considerations](@@env.BASE_URL@@/connector/considerations-non-ad) +- [High Availability](@@env.BASE_URL@@/connector/high-availability) +- [Updating the Connector](@@env.BASE_URL@@/connector/update) +- [Monitoring with System Center Operations Manager](@@env.BASE_URL@@/connector/scom-monitoring) +- [Troubleshooting](@@env.BASE_URL@@/connector/troubleshooting) diff --git a/docs/connector/considerations-non-ad.md b/docs/connector/considerations-non-ad.md new file mode 100644 index 00000000..529a6e74 --- /dev/null +++ b/docs/connector/considerations-non-ad.md @@ -0,0 +1,11 @@ +# Considerations for OpenLDAP and non-AD directories + +The connector comes by default highly optimized for **Active Directory**. To configure it for OpenLDAP or other LDAP directories you will have to customize these settings in the **config.json** file: + +``` + "LDAP_USER_BY_NAME": "(cn={0})", + "LDAP_SEARCH_QUERY": "(&(objectClass=person)(cn={0}))", + "LDAP_SEARCH_ALL_QUERY": "(objectClass=person)", +``` + +In some cases, instead of **cn** it might be better to use **uid**. diff --git a/docs/connector/high-availability.md b/docs/connector/high-availability.md new file mode 100644 index 00000000..ea3027e4 --- /dev/null +++ b/docs/connector/high-availability.md @@ -0,0 +1,21 @@ +# High availability + +The connector is a very important component, therefore we recommend a highly available deployment through redundancy: installing multiple instances of it. + +If one of the instances fails either because of a network issue or hardware issue, Auth0 will redirect the login transactions to the other connector. + +Having a highly available deployment also allows updating the connector with zero downtime. + +## Instructions + +1. Install the connector as explained [here](@@env.BASE_URL@@/connector/install). +2. Make sure all steps are complete and the connector is up and running. +3. Copy **config.json**, **lib/profileMapper.js** and everything under the **certs** folder from: + - Windows: `C:\Program Files (x86)\Auth0\AD LDAP Connector\` + - Linux: `/opt/auth0/ad-ldap` + +4. Install the connector on a second server follwing the same instructions as above. When prompted for the __Ticket URL__ close the dialog +5. On the **second server**, overwrite the default files, `config.json`, `lib/profileMapper.js` and the files in `certs` directory, with the versions of those files from the **first server** that were copied/saved in step 3 above. +6. Restart the service on the second server. + +You should see now 2 connectors on the dashboard. \ No newline at end of file diff --git a/docs/connector/install-other-platforms.md b/docs/connector/install-other-platforms.md new file mode 100644 index 00000000..e36a1885 --- /dev/null +++ b/docs/connector/install-other-platforms.md @@ -0,0 +1,25 @@ +# Installing the AD LDAP Connector on different platforms + +1. [Install node.js v0.10](https://nodejs.org). +2. Use the GitHub repository to download the package: to /tmp. Eg:
+3. Expand the package and install dependencies: + + mkdir /opt/auth0-adldap + tar -xzf /tmp/adldap.tar.gz -C /opt/auth0-adldap --strip-components=1 + cd /opt/auth0-adldap + +4. Run `node server.js` and follow the steps to finish the process. +5. Once the connector is running you will need to daemonize the connector using a tool like upstart, systemd, init.d, etc. + + + \ No newline at end of file diff --git a/docs/connector/install.md b/docs/connector/install.md new file mode 100644 index 00000000..701286d9 --- /dev/null +++ b/docs/connector/install.md @@ -0,0 +1,67 @@ +# Installing the Connector on Windows + +## Download the installer + +The Connector is packaged as a standard installer file (__MSI__). Download from here: + +
+ + + + +## Run the installer + +Run the installer and follow the instructions: + +![](https://cdn.auth0.com/docs/img/adldap-connector-setup.png) + +The __AD/LDAP Connector__ in Windows is installed as a Windows Service: + +![](https://cdn.auth0.com/docs/img/adldap-connector-services.png) + +## Link to Auth0 + +Once the installation is complete, you will see the following screen in a browser pointing to localhost: + +![](https://cdn.auth0.com/docs/img/adldap-connector-admin-ticket.png) + +Enter the __TICKET URL__ provided when you provisioned the connection. + +The __TICKET URL__ uniquely identifies this connector in Auth0. The Connector will use this to communicate with Auth0 Server and automatically complete the configuration. + + +## Link to LDAP + +Once you have entered the __TICKET URL__, you must enter the LDAP settings: + +![](https://cdn.auth0.com/docs/img/adldap-connector-admin-settings.png) + +- **LDAP Connection String (eg: ldap://ldap.internal.contoso.com):** This is the protocol + the domain name or ip address of your LDAP server. The protocol can be either `ldap` or `ldaps`. If you need to use `ldaps` make sure that the certificate is valid in the current server. +- **Base DN (eg: dc=contoso,dc=com):** This is the base container for all the queries performed by the connector. +- **Username (eg: cn=svcauth0,dc=services,dc=contoso,dc=com):** The full distinguish name of a user to perform queries. +- **Password:** The password of the user. + +Once you submit the above information, the connector will perform a series of tests: + +![](https://cdn.auth0.com/docs/img/adldap-connector-admin-settings-ok.png) + +Make sure that all tests are in green. + +Congratulations, your connector is installed and ready to use. \ No newline at end of file diff --git a/docs/connector/overview.md b/docs/connector/overview.md new file mode 100644 index 00000000..6a8a8735 --- /dev/null +++ b/docs/connector/overview.md @@ -0,0 +1,7 @@ +# Overview + +Auth0 integrates with Active Directory/LDAP through the __Active Directory/LDAP Connector__ that you install in your network. + +The __AD/LDAP Connector (1)__, is a bridge between your __Active Directory (2)__ and the __Auth0 Service (3)__. This bridge is necessary because AD is typically locked down to your internal network, and Auth0 is a cloud service running on a completely different context. + + diff --git a/docs/connector/prerequisites.md b/docs/connector/prerequisites.md new file mode 100644 index 00000000..57c9bbc8 --- /dev/null +++ b/docs/connector/prerequisites.md @@ -0,0 +1,27 @@ +# Prerequisites + +## Internet Connectivity + +The connector must be installed on a server with outbound connectivity to at the very least `https://@@account.namespace@@` on port **443**. + +The connector can be installed and configured behind a __proxy server__ but we don't recommend this. + +No inbound rules are required unless **Kerberos** or **Certificate authentication** is enabled. In these cases, the server(s) where the connector is installed on must be reachable from your users browsers. If more than one instance of the connector is installed, you should use a load balancer to direct traffic to one connector and the other. + +It is very important to have the server clock automatically synchronized with an NTP server. Otherwise the connector will fail to start and report __clock skew error__. + +## Connectivity to LDAP + +The connector must be installed on a server with access to the LDAP server on port **389 for ldap** or **636 for ldaps**. + +## Hardware requirements + +- **Architecture**: x86 or x86-64 +- **CPU cores**: min. 1, recommended 2 +- **Storage**: 500MB of free space on disk +- **Operating System**: The connector can run on Windows or Linux +- **RAM**: min. 2GB + +## Windows Version + +The connector can run on Windows 7+ or Windows 2008R2+. We recommend Windows 2012. diff --git a/docs/connector/scom-monitoring.md b/docs/connector/scom-monitoring.md new file mode 100644 index 00000000..6f756c5a --- /dev/null +++ b/docs/connector/scom-monitoring.md @@ -0,0 +1,19 @@ +# Monitoring the AD/LDAP Connector with System Center Operations Manager + +The Auth0 AD/LDAP connector can run as a Service on Windows based machines. + +You can monitor the service status using System Center as you would do with any other service. + +* Open the __Add Monitoring Wizard__ and select the __Monitoring Type: Windows Service__: + +![ss-2014-12-11T22-48-51.png](https://s3.amazonaws.com/blog.auth0.com/ss-2014-12-11T22-48-51.png) + +* Enter a name and description: + +![ss-2014-12-11T22-49-57.png](https://s3.amazonaws.com/blog.auth0.com/ss-2014-12-11T22-49-57.png) + +* Select the Server in which the AD/LDAP Connector is installed and then choose "Auth0 ADLDAP": + +![ss-2014-12-11T22-50-37.png](https://s3.amazonaws.com/blog.auth0.com/ss-2014-12-11T22-50-37.png) + +* Select the limits of **CPU** and **Memory limits**. Eg: 10% of CPU and 200MB of RAM are good limits to trigger alerts. diff --git a/docs/connector/troubleshooting.md b/docs/connector/troubleshooting.md new file mode 100644 index 00000000..661c2b49 --- /dev/null +++ b/docs/connector/troubleshooting.md @@ -0,0 +1,45 @@ +# Troubleshooting + +We do our best to support many scenarios and different configurations. + +Unfortunately some issues are very hard to predict. Especially those that happen behind our customer's firewall. We have less control over that environment, and the related infrastructure dependencies (e.g. network, proxies, OS versions, etc). + +If you are experiencing problems with the connector, please send us an email to [support](mailto:support@auth0.com) with the following information: + +- Symptoms, explain your problem +- the `config.json` from: + - Windows: **C:\Program Files (x86)\Auth0\AD LDAP Connector\config.json** + - Linux: **/opt/auth0-adldap/config.json** +- the service log files from: + - Windows: **C:\Program Files (x86)\Auth0\AD LDAP Connector\logs.log** + - Linux: **/var/log/auth0-adldap.log** + +These logs are not accessible to us directly. + +It is usually a good idea to keep the connector up to date. You can check the version of the installed connector on the Dashboard: + +![](https://cdn.auth0.com/docs/img/adldap-connector-version.png) + +##Common issues + +These are the most common problems: + +### Clock skew + +Make sure the clock of your server is current. + +If the time is not correct, it will cause authentication requests to fail. This can be fixed by ensuring that the System is properly configured to use to pool a sync server via the NTP (Network Time Protocol). + +Note: on windows environments the ntp provider is usually the same domain controller. Make sure that your Domain Controller is synchronized with some external service. + +### No internet connectivity + +`https://@@account.namespace@@` should be reachable from the server. + +If proxies are installed, make sure they are configured correctly. + +A quick test for this is to open a browser pointing to [https://@@account.namespace@@/test](https://@@account.namespace@@/test). + +### Service account permissions + +The Service account used to configure the connector must have read permissions on the AD/LDAP server, as well as capable of querying groups for users. diff --git a/docs/connector/update.md b/docs/connector/update.md new file mode 100644 index 00000000..798210cb --- /dev/null +++ b/docs/connector/update.md @@ -0,0 +1,60 @@ +# Updating the AD/LDAP Connector + +If there are multiple instances of the AD/LDAP Connector in a deployment, it is recommended that the set of steps below be done to each instance, one at a time, so that only one instance is down at any point in time. + +These are the steps to update the AD/LDAP Connector to the latest version: + +###1. Verify the version you have installed + +Hover over the Connector status indicator on the dashbaord: + +![](https://cdn.auth0.com/docs/img/adldap-connector-version.png) + +The tooltip will indicate the current status and the installed version. + +###2. Download the latest version + +The latest released version of the connector is . + +Download the Windows Installer from . The sha1 checksum is: + +
+ +Use the GitHub repository for other platforms: . + +**Note:** Always verify the checksum of the downloaded installer as explained [here](@@env.BASE_URL@@/checksum). + +###3. Backup your current config + +Before updating the connector backup these files from `%Program Files(x86)%\Auth0\AD LDAP Connector\`: + +* `config.json` +* `certs` folder +* `lib\profileMapper.js` **only if you modified this file manually** + +> The PATH above works for Windows based machines. Installations in other platforms will be located somewhere else, but contain the same assets. + +###4. Run the installer + +Start the installer and follow the instructions. + +Close the configuration dialog without changing anything. + +###5. Restore files + +Copy all the files from __Step 2__ into `%Program Files(x86)%\Auth0\AD LDAP Connector\`. + +Restart the **"Auth0 AD LDAP"** service from the service console. + + diff --git a/docs/custom-authentication.md b/docs/custom-authentication.md index 9fbda635..d49cfdce 100644 --- a/docs/custom-authentication.md +++ b/docs/custom-authentication.md @@ -16,21 +16,21 @@ In order to install the __Auth0 Custom Authentication Connector__ you need to fi
  • - + Windows Installer node-v0.8.22-x86.msi
  • - + Macintosh Installer node-v0.8.22.pkg
  • - + Linux node-v0.8.22.tar.gz @@ -44,7 +44,7 @@ Once node.js has been installed, download and unzip the source code for the __Au
    • - + Auth0 Custom Authentication Connector (zip) source code zip file @@ -62,7 +62,7 @@ When prompted for the ticket url, paste the following: https://@@account.namespace@@/p/custom/@@ticket@@ -> After entering the ticket, the connector will exchange trust information (like URLs, endpoints, etc.) with the server to complete the setup on Auth0. +> After entering the ticket, the connector will exchange trust information (like URLs, endpoints, etc.) with the server to complete the setup on Auth0. ##2. Let's try to login! @@ -70,7 +70,7 @@ Now that you have a running authentication server, let's try to login with a tes  Test Login -- Test User: __foo@bar.com__ +- Test User: __foo@bar.com__ - Test Password: __123__ > By default, the connector will only allow one user to login: a __test__ user that is hard coded in the app. This is so you can verify that everything works fine before changing it to use a real user repository (like a SQL database). @@ -91,7 +91,7 @@ To change the authentication logic, you will have to edit `users.js` // lookup a user // validate password // return user with profile - + return callback(null, { id: 123, username: 'test', displayName: 'test user', ... }); }; @@ -127,7 +127,7 @@ For your reference, here are tutorials you can follow to learn more about deploy ### Production considerations -To avoid man in the middle attacks, this server has to be configured to use TLS/SSL. If you are running under IIS, configure the [web site to use SSL](http://www.iis.net/learn/manage/configuring-security/how-to-set-up-ssl-on-iis). +To avoid man in the middle attacks, this server has to be configured to use TLS/SSL. If you are running under IIS, configure the [web site to use SSL](http://www.iis.net/learn/manage/configuring-security/how-to-set-up-ssl-on-iis). If you are hosting on Linux, change the [server.js](https://github.com/auth0/ad-ldap-connector/blob/master/server.js) to use an [https server in node.js](http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener). @@ -152,11 +152,11 @@ var checkStep = function () { for (var i = 1; i < currentStep; i++) { $('h2:contains(' + i + '.)') .addClass('step-finished') - .prepend(''); + .prepend(''); }; $('.current-step').removeClass('current-step'); - + $('h2:contains(' + currentStep + '.)').addClass('current-step'); if (currentStep === 3 && $('#logmeout3').length === 0) { diff --git a/docs/custom-signup.md b/docs/custom-signup.md new file mode 100644 index 00000000..0997c39d --- /dev/null +++ b/docs/custom-signup.md @@ -0,0 +1,97 @@ +# Custom Signup + +In some cases, you may want to customize the user sign up form with more fields. The [Auth0Lock](lock) has a `signup` mode but it does not support adding arbitrary fields, so you will have to implement your own UI for signup. Note that you can still use the Auth0Lock for signin though. + +> You can find the source code of this example in [this github repository](https://github.com/auth0/node-auth0/tree/master/examples/custom-signup). + +### 1. Create the following HTML for signup: + +```html +
      +
      + Create a new user + + + + + +
      +
      +``` + +Notice that `color` and `food` are custom fields and we will be storing them too. + +### 2. Add the Auth0Lock to it + +```html + +``` + +```js +window.lock = new Auth0Lock('@@account.clientId@@', '@@account.namespace@@'); + +window.lock.getClient()._callbackURL = '@@account.callback@@'; +window.lock.getClient()._callbackOnLocationHash = true; +``` + +### 3. Bind the submit event + +Then, bind the `submit` event of the form so the user data is sent to our server side. + +```js +$('#create-user').submit(function (event) { + if (event && event.preventDefault) { + event.preventDefault(); + } + + var userData = {}; + + userData.email = $('#create-user input[name=email]').val(); + userData.password = $('#create-user input[name=password]').val(); + userData.color = $('#create-user input[name=color]').val(); + userData.food = $('#create-user input[name=food]').val(); + + $.ajax({ + url: '/signup', + type: 'POST', + data: JSON.stringify(userData), + contentType: 'application/json', + dataType: 'json', + success: function(xhr, status) { + // We are login the user programatically after creating it + lock.getClient().login({ + 'username': userData.email, + 'password': userData.password, + 'connection': 'Username-Password-Authentication' + }, function (err, profile, id_token, access_token) { + // store the profile in localstorage/cookie + }); + }, + error: function () { + // TODO handle signup error + } + }); + + return false; +}); +``` + + +### 4. Server Side + +After receiving the request from the client, the JSON contained in the body of the message must be enriched with the `connection` field, that indicates in which connection the user must be stored. `POST` that JSON to `/api/users` and in case that succeeds the user is created. + + + POST /api/users + Authorization: Bearer .... access_token .... + + { + email: "...", // taken from request + password: "...", + color: "...", + food: "...", + connection: "..." // added by the server + } + +> Bear in mind that before doing that you may need to generate an access token. Check the API section for more information. +> In case you are using any of our bindings for Node.js, ASP.NET you may use those instead of doing the HTTP requests manually. diff --git a/docs/dwolla-clientid.md b/docs/dwolla-clientid.md new file mode 100644 index 00000000..aebc3d3a --- /dev/null +++ b/docs/dwolla-clientid.md @@ -0,0 +1,4 @@ +# Obtaining a Client ID and Client Secret for Dwolla + +This page is work in progress. Please contact us if you are interested in Dwolla connectivity: [support@auth0.com](mailto://support@auth0.com) + diff --git a/docs/ember-tutorial.md b/docs/ember-tutorial.md new file mode 100644 index 00000000..b2f4526d --- /dev/null +++ b/docs/ember-tutorial.md @@ -0,0 +1,7 @@ +# Using Auth0 in Ember.js applications + +Read the [Single Page Application tutorial](singlepageapp-tutorial) to get an idea of how Auth0 works in this scenario. + +There is nothing in particular that needs to be done in Ember, other than instantiating the [Auth0Lock](https://github.com/auth0/lock) or the [js wrapper](https://github.com/auth0/auth0.js). Once the user has been authenticated, you can save the profile in a cookie or local storage. The `id_token` can be used to call your APIs and flow the user identity. + +Also, here is an [Ember.js example](https://github.com/kiwiupover/ember-auth0) for your reference. This example uses [auth0.js](https://github.com/auth0/auth0.js). diff --git a/docs/enable-simple-connection.md b/docs/enable-simple-connection.md index fc63f563..621fd37e 100644 --- a/docs/enable-simple-connection.md +++ b/docs/enable-simple-connection.md @@ -9,22 +9,22 @@ This tutorial will use the simplest identity provider (Google OAuth2) that requi ##1. Login into Auth0 Login and select the __Social__ option under the __Connections__ tab: -![](img/connection-add.png) +![](//cdn.auth0.com/docs/img/connection-add.png) -##2. Enable the Connection +##2. Enable the Connection Choose _Google/Gmail_ and change status to __Enabled__. Notice all attributes about the user that are supplied by Google, select the ones you are interested in: -![](img/connection-add-idp-attributes.png) +![](//cdn.auth0.com/docs/img/connection-add-idp-attributes.png) Save the connection. You are done! ##3. Test the new connection Click on the __Test__ button: -![](img/connection-add-idp-test.png) +![](//cdn.auth0.com/docs/img/connection-add-idp-test.png) You will be redirected to Google for authentication and after that you will return to Auth0 where your user profile will be displayed: -![](img/connection-add-idp-test-r.png) +![](//cdn.auth0.com/docs/img/connection-add-idp-test-r.png) > For full integration with Google OAuth2, you need to obtain a `clientId` and a `clientSecret`. For a quick setup you don't need it. See [here](goog-clientid) for details. diff --git a/docs/evernote-clientid.md b/docs/evernote-clientid.md new file mode 100644 index 00000000..af773ee3 --- /dev/null +++ b/docs/evernote-clientid.md @@ -0,0 +1,3 @@ +# Obtaining an API Consumer Key and Consumer Secret for Evernote + +To configure Evernote OAuth connections you will need to register Auth0 with Evernote on their [developer portal](http://dev.evernote.com/). \ No newline at end of file diff --git a/docs/exact-clientid.md b/docs/exact-clientid.md new file mode 100644 index 00000000..bc7a7904 --- /dev/null +++ b/docs/exact-clientid.md @@ -0,0 +1,15 @@ +# Obtaining an Client Id and Client Secret for Exact + +To configure an Exact OAuth2 connection you will need to register your Auth0 tenant on their [developer portal](https://apps.exactonline.com/). + +##1. Log in into the developer portal and register a new App. + +##2. Edit your App Properties. + +Enter your app name. You will also be asked for a `Callback URL`. Use this value: + + https://@@account.namespace@@/login/callback + +##3. Copy `client_id` and `client_secret` to the Auth0 dashbaord. + +> You can register applications in multiple regions with Exact. By default Auth0 will use `https://start.exactonline.nl`, but this value can be overriden with the `Base URL` parameter. diff --git a/docs/facebook-clientid.md b/docs/facebook-clientid.md index a6ca31c3..dedddb6e 100644 --- a/docs/facebook-clientid.md +++ b/docs/facebook-clientid.md @@ -5,25 +5,27 @@ To configure Facebook connections you will need to register Auth0 with Facebook ##1. Log in into Facebook Developers Go to the [Facebook Developers Apps](https://developers.facebook.com/apps), login with your account and click on the __Create New App__ button: -![](img/facebook-1.png) +![](//cdn.auth0.com/docs/img/facebook-1.png) --- ##2. Give a name to your Application -![](img/facebook-2.png) +![](//cdn.auth0.com/docs/img/facebook-2.png) --- -##3. Setup the callback URL: +##3. Complete setup -Enter the following URL on the "Website with Facebook Login" section and click **Save**. +Copy the App ID and App Secret to the clipboard: - https://@@account.namespace@@/login/callback +![](//cdn.auth0.com/docs/img/facebook-3.png) + +On app **Settings > Advanced**, enter the following URL on the "Valid OAuth redirect URIs": -![](img/facebook-3.png) + https://@@account.namespace@@/login/callback -And copy the App ID and App Secret to the clipboard. +![](//cdn.auth0.com/docs/img/facebook-3b.png) --- @@ -31,6 +33,10 @@ And copy the App ID and App Secret to the clipboard. Once the application is registered, enter your new `App ID` and `App Secret` into the Facebook connection settings in Auth0. -![](img/facebook-4.png) +![](//cdn.auth0.com/docs/img/facebook-4.png) **That's it!** You can try it with the tester now. + +##5. Set the Facebook application to Live + +Before your users can login with the new facebook application, you must set it to live via the "Status & Review" tab in the facebook developer page. diff --git a/docs/fitbit-clientid.md b/docs/fitbit-clientid.md index 7cf6fa38..d58305ba 100644 --- a/docs/fitbit-clientid.md +++ b/docs/fitbit-clientid.md @@ -5,7 +5,7 @@ To configure a Fitbit connection you will need to register a new application in ##1. Log in into Fitbit's Developers site Log in into [Fitbit's Developer site](https://dev.fitbit.com), select __REGISTER AN APP__: -![](img/fitbit-register-1.png) +![](//cdn.auth0.com/docs/img/fitbit-register-1.png) --- @@ -21,4 +21,4 @@ Your callback URL into Auth0 is: Once the application is registered, enter your new `Consumer Key` and `Consumer Secret` into the connection settings in Auth0. ->Fitbit is a registered trademark and service mark of Fitbit, Inc. Auth0 is designed for use with the Fitbit platform. This product is not put out by Fitbit, and Fitbit does not service or warrant the functionality of this product. \ No newline at end of file +>Fitbit is a registered trademark and service mark of Fitbit, Inc. Auth0 is designed for use with the Fitbit platform. This product is not part out by Fitbit, and Fitbit does not service or warrant the functionality of this product. \ No newline at end of file diff --git a/docs/github-clientid.md b/docs/github-clientid.md index 91bd010d..56c08b8d 100644 --- a/docs/github-clientid.md +++ b/docs/github-clientid.md @@ -5,13 +5,13 @@ To configure a GitHub connection you will need to register Auth0 with GitHub. ##1. Add a new Application Log in into GitHub and [register an new Application](https://github.com/settings/applications/new): -![](img/github-addapp-1.png) +![](//cdn.auth0.com/docs/img/github-addapp-1.png) --- ##2. Complete information about your instance of Auth0 -![](img/github-addapp-2.png) +![](//cdn.auth0.com/docs/img/github-addapp-2.png) The callback address for your app should be: @@ -23,7 +23,7 @@ The callback address for your app should be: Once the application is registered, enter your new `ClientId` and `ClientSecret` into the connection settings in Auth0. -![](img/github-addapp-3.png) +![](//cdn.auth0.com/docs/img/github-addapp-3.png) -![](img/github-addapp-4.png) +![](//cdn.auth0.com/docs/img/github-addapp-4.png) diff --git a/docs/goodreads-clientid.md b/docs/goodreads-clientid.md new file mode 100644 index 00000000..3c724c07 --- /dev/null +++ b/docs/goodreads-clientid.md @@ -0,0 +1,22 @@ +# Obtaining a Consumer and Secret Keys for Goodreads + +##1. Log in into Goodreads's developers site +Log in into [Goodreads's Developer site](https://www.goodreads.com/api), select __developer key__: + +![](//cdn.auth0.com/docs/img/goodreads-register-1.png) + +--- + +##2. Complete information about your instance of Auth0 + +Use this for the `Callback URL`: + + https://@@account.namespace@@/login/callback + +--- + +##4. Get your Consumer Key and Consumer Secret + +Once the application is registered, enter your new `Key` and `Secret` into the connection settings in Auth0. + +![](//cdn.auth0.com/docs/img/goodreads-register-2.png) diff --git a/docs/goog-clientid.md b/docs/goog-clientid.md index 29626ae3..45ff5c38 100644 --- a/docs/goog-clientid.md +++ b/docs/goog-clientid.md @@ -2,38 +2,68 @@ To configure Google OAuth connections (Google Apps and Google) you will need to register Auth0 with Google on the API Console. -##1. Log in into API Console -Go to the [API Console](https://code.google.com/apis/console#access), and click on the __Create an OAuth2 client ID__ button: +##1. Log in API Console +Go to the [API Console](https://console.developers.google.com), and click __Create Project__. -![](img/goog-apiconsole-1.png) +A dialog like the one shown in the following figure will be displayed. ---- +![](//cdn.auth0.com/docs/img/goog-api-app-empty.png) ##2. Complete information about your instance of Auth0 -![](img/goog-apiconsole-2.png) +Provide your app's information and click **Create**. + +> The information provided in this dialog is not related to Auth0. It is only useful for your reference + +![](//cdn.auth0.com/docs/img/goog-api-app-info.png) + +An activity will begin, as shown in the following figure. Once the activity is completed you will be redirected to the project's dashboard to continue with the next steps. -These URLs and information don't need to point to Auth0. It is information for your reference. +![](//cdn.auth0.com/docs/img/goog-api-creation-activity.png) --- -##3. Setup your callback URL into Auth0: +##3. Enable the Google+ API - https://@@account.namespace@@/login/callback +Click **Enable an API** and locate the **Google+ API** item in the list. -![](img/goog-apiconsole-3.png) +![](//cdn.auth0.com/docs/img/goog-api-plus-off.png) + +Enable it by clicking **OFF**. + +![](//cdn.auth0.com/docs/img/goog-api-plus-on.png) --- -##4. Get your ClientId and ClientSecret +##4. Set up Consent Screen + +Under APIs & auth, go to **Consent Screen**. Here, enter your **Product Name** that will be shown when users try to log in through Google: + +![](//cdn.auth0.com/docs/img/goog-api-product-name.png) + +If this field is not present, your users may see errors such as `invalid_client: no application name` when attempting to log in. + + +##5. Get your ClientId and ClientSecret + +Click **Credentials** in the left sidebar and then click **Create new Client ID**. + +![](//cdn.auth0.com/docs/img/goog-api-credentials.png) + +Enter your tenant's information. That means replacing the appearences of **"YOURTENANT"** shown in the following figure with the tenant you created. + +![](//cdn.auth0.com/docs/img/goog-api-client-creation.png) + +Click **Create Client ID**. The resulting settings will be displayed: -Once the application is registered, enter your new `ClientId` and `ClientSecret` into the connection settings in Auth0. +![](//cdn.auth0.com/docs/img/goog-api-client-settings.png) -![](img/goog-apiconsole-4.png) +You can now use the `ClientId` and `ClientSecret` for your Auth0 connection's settings. +##6. Enable Admin SDK Service -##5. Enable Admin SDK Service +If you are planning to connect Google Apps enterprise domains, you need to enable the __Admin SDK__ service. -If you are planning to connect Google Apps enterprise domains, you need to enable the __Admin SDK__ service as follows: +To do so, click **API** in the left sidebar, locate the **Admin SDK** item and click **OFF** to turn it on. -![](img/goog-apiconsole-5.png) +![](//cdn.auth0.com/docs/img/goog-api-admin-sdk.png) diff --git a/docs/google-admin-sdk.md b/docs/google-admin-sdk.md new file mode 100644 index 00000000..2ff19099 --- /dev/null +++ b/docs/google-admin-sdk.md @@ -0,0 +1,11 @@ +# Configuration needed to query users from a Google Apps domain + +To query Google Apps domain users, you need to enable the following: + +1. An administrator of a Google Apps domain needs to login to the Google Admin console, go to Security, select API Reference and check "Enable API Access". + + + +2. The owner of the Client ID (the developer of the application) on Google API Console needs to enable the Admin SDK API. + + diff --git a/docs/har.md b/docs/har.md new file mode 100644 index 00000000..68dcca6f --- /dev/null +++ b/docs/har.md @@ -0,0 +1,15 @@ +## Throubleshooting with HAR files + +Sometimes during development the authentication flow doesn't work as expected. + +The fastest way to identify the underlying issue is to export a `HAR` file from Google Chrome Dev Tools. An **HTTP Archive (HAR) format** file, is a JSON formatted log file of all web browser interactions with web servers. + +###Generating a HAR file: + +1. Close all __incognito__ windows from Google Chrome. +2. Open a __new incognito__ tab on Google Chrome. +3. Open __Google Chrome Developers Tools__ on the new Incognito Window and click the Network Tab. Make sure you check the __Preserve Log__ options to record all interactions. +4. Proceed with the navigation that presents issues. +5. When complete, go back to the __Network__ tab, right click and then select **Save as HAR**: ![docs/img/ss-2015-01-19T11-32-15.png](https://cdn.auth0.com/docs/img/ss-2015-01-19T11-32-15.png) +6. Before sending the HAR file to us, make sure to obfuscate any sensitive information using a text editor (e.g. remove passwords, client_secrets, etc). +7. Send the file to [support](mailto://support.auth0.com?subject=HAR file) \ No newline at end of file diff --git a/docs/hrd.md b/docs/hrd.md new file mode 100644 index 00000000..c4bfb78f --- /dev/null +++ b/docs/hrd.md @@ -0,0 +1,113 @@ +# Selecting the connection in Auth0 for multiple login options + +Auth0 allows you to offer your users multiple ways of authenticating. This is especially important with SaaS, multitenant apps in which a single app is used by many different organizations, each one potentially using different systems: LDAP, Active Directory, Google Apps, or username/password stores. + +![](https://docs.google.com/drawings/d/1h3-gOOLOEOzbqh5c3n_9p7YNHpkffHNH7nFOgG3KM8A/pub?w=744&h=307) + +> Selecting the appropriate Identity Providers from multiple options is called "Home Realm Discovery". A pompous name for a simple problem. + +##Option 1: programmatically +When you initiate an authentication transaction with Auth0 you can optionally send a `connection` parameter. This value maps directly with any __connection__ defined in your dashboard. + +If using the [Auth0Lock](lock), this is as simple as writing: + + auth0.show({connections: ['YOUR_CONNECTION']}); + + +Notice that this is equivalent of just navigating to: + + https://@@account.namespace@@/authorize/?client_id=@@account.clientId@@&response_type=code&redirect_uri=@@account.callback@@&state=OPAQUE_VALUE&connection=YOUR_CONNECTION + +There are multiple practical ways of getting the `connection` value. Among the most common ones: + +* You can use __vanity URLs__: `https://{connection}.yoursite.com` or `https://www.yoursite.com/{connection}` +* You can just ask the user to pick from a list (notice [there's an API](@@base_url@@/api#!#get--api-connections) to retrieve all connections available) + +> These two methods assume it is acceptable for your app to disclose the names of all companies you are connected to. Sometimes this is not the case. + +* You could use non-human-readable connection names and use some external mechanism to map these to users (e.g. through a primary verification, out of band channel for example). + +##Option 2: using email domains with the Auth0Lock + +The [Auth0Lock](lock) has built in functionality for identity provider selection. For social connections it will show logos for all those enabled in that particular app. + +An additional feature in the Auth0Lock is the use of email domains as a way of routing authentication requests. Enterprise connections in Auth0 can be mapped to `domains`. For example, when configuring an ADFS or a SAML-P identity provider: + +![](https://cldup.com/k_LcfC8PHp.png) + +If a connection has this setup, then the password textbox gets disabled automatically when typing an e-mail with a mapped domain: + +![](https://cldup.com/R7mvAZpSnf.png) + +In the example above the domain `companyx.com` has been mapped to an enterprise connection. + +Notice that you can associate multiple domains to a single connection. + +##Option 3: adding custom buttons to the Auth0Lock + +Using the [Auth0Lock](lock)'s [support for customization and extensibility](https://github.com/auth0/lock/wiki/Auth0lock-customization) it's also possible to add buttons for your Enterprise Connections. Here's an example of adding a button for an Azure AD connection to the Lock: + +``` +var lock = new Auth0Lock(cid, domain); +lock.once('signin ready', function() { + var link = $('Login with Fabrikam Azure AD'); + link.appendTo('.a0-iconlist'); + link.on('click', function() { + lock.getClient().login({connection: 'fabrikamdirectory.onmicrosoft.com'}); + }); +}); + +lock.show({ + connections: ['facebook', 'google-oauth2', 'windows-live'] +}); +``` + +This is useful when you want to give users a consistent login experience where they click on the connection they want to use. + +![](//cdn.auth0.com/docs/img/hrd-custom-buttons-lock.png) + +The Auth0Lock's stylesheet contains the following provider icons which can be used when adding custom buttons: + +``` +.a0-amazon +.a0-aol +.a0-baidu +.a0-box +.a0-dropbox +.a0-dwolla +.a0-ebay +.a0-evernote +.a0-exact +.a0-facebook +.a0-fitbit +.a0-github +.a0-gmail +.a0-google +.a0-googleplus +.a0-guest +.a0-ie +.a0-instagram +.a0-linkedin +.a0-miicard +.a0-office365 +.a0-openid +.a0-paypal +.a0-planningcenter +.a0-renren +.a0-salesforce +.a0-sharepoint +.a0-shopify +.a0-soundcloud +.a0-stackoverflow +.a0-thecity +.a0-thirtysevensignals +.a0-twitter +.a0-vk +.a0-waad +.a0-weibo +.a0-windows +.a0-wordpress +.a0-yahoo +.a0-yammer +.a0-yandex +``` \ No newline at end of file diff --git a/docs/identityproviders.md b/docs/identityproviders.md index 0e9f01f6..e35301a0 100644 --- a/docs/identityproviders.md +++ b/docs/identityproviders.md @@ -2,34 +2,57 @@ Auth0 is an "identity hub" that supports a number of authentication providers using different protocols: OAuth2, WS-Federation, etc. -Out of the box, Auth0 supports: - -1. Enterprise: - * __Active Directory__ - * __ADFS__ - * __LDAP__ - * __Google Apps__ - * __Office365__ - * __SQL__ - * Any __SAML-P__ or __WS-Federation__ system - -2. Social: - * __Amazon__ - * __Facebook__ - * __LinkedIn__ - * __Twitter__ - * __Microsoft Account__ (formerly LiveID) - * __Google__ - * __PayPal__ - * __GitHub__ - * __vKontakte__ - * __37Signals__ - * __Box__ - * __Salesforce__ - * __Fitbit__ - - -Auth0 sits in between your app and the system that authenticate your users (any of the above). Through this level of abstraction, Auth0 keeps your app isolated from changes in each provider's implementation and idiosyncracies. An additional benefit is the [normalized user profile](user-profile) that simpifies user management. +Out of the box, Auth0 supports the following: + +## Enterprise + * __Active Directory__ + * __ADFS__ + * __LDAP__ + * __Google Apps__ + * __Office365__ + * __SQL__ + * __PingFederate__ + * Any __SAML-P__ or __WS-Federation__ system + * __SharePoint Online Apps__ (beta) + * __IP Address based Authentication__ (beta) + +## Social + * __Amazon__ + * __Facebook__ + * __LinkedIn__ + * __Twitter__ + * __Microsoft Account__ (formerly LiveID) + * __Google__ + * __PayPal__ + * __Yahoo!__ + * __GitHub__ + * __vKontakte__ + * __Yandex__ + * __37Signals__ + * __Box__ + * __Salesforce__ + * __Salesforce (sandbox)__ + * __Fitbit__ + * __Baidu__ + * __RenRen__ + * __Weibo__ + * __AOL__ + * __Shopify__ + * __WordPress__ + * __Dwolla__ + * __miiCard__ + * __Yammer__ + * __SoundCloud__ + * __Instagram__ + * __Evernote__ + * __Evernote (sandbox)__ + * __The City__ + * __The City (sandbox)__ + * __Planning Center__ + +## Additional Information + +Auth0 sits in between your app and the system that authenticates your users (any of the above.) Through this level of abstraction, Auth0 keeps your app isolated from changes in each provider's implementation and idiosyncracies. An additional benefit is the [normalized user profile](user-profile) that simpifies user management. > The relationship between Auth0 and each of these authentication providers is called a 'connection' diff --git a/docs/includes/apinote.md b/docs/includes/apinote.md new file mode 100644 index 00000000..910b528d --- /dev/null +++ b/docs/includes/apinote.md @@ -0,0 +1,5 @@ +When consuming an API there are two things to consider: how to **validate the token (1)** and **getting a token (2)**. + +## 1. Securing the API + +On the server, follow these steps to secure the API. \ No newline at end of file diff --git a/docs/includes/callapi.md b/docs/includes/callapi.md new file mode 100644 index 00000000..d128bda3 --- /dev/null +++ b/docs/includes/callapi.md @@ -0,0 +1,29 @@ +--- + +## 2. Calling an API with a JSON Web Token + +### Single Page Applications / HTML5 JavaScript Front End + +The `JWT` is available on the location hash of the browser as the `id_token` parameter. You probably want to store it in local/session storage or a cookie `auth0.getProfile(location.hash, ...)`. Read more... + +### Native Mobile Applications + +The `JWT` is available after login. Each native SDK should return a `user` containing the token as a property. For instance, on iOS, you would use `client.auth0User.IdToken`. Read more... + +### Web Applications (Server Side w/Cookies) + +The `JWT` is available on the response when exchanging the `code`. Typically this is handled by the SDK. For instance, in ASP.NET you would do `ClaimsPrincipal.Current.Claims.FindFirst("id_token")`. + +### Programmatic Authentication (Service Accounts) + +A `JWT` can be obtained authenticating with a user from a Database or AD/LDAP connection by calling the `oauth/ro` endpoint of the Authentication API, passing a username, password, connection and client_id. This can be used to obtain tokens from any system without user intervention (e.g. scripts, batch files, web backends, native apps). Look under **SDK - Authentication API - Database & Active Directory / LDAP Authentication** (on the dashboard). + +### Delegated Authentication + +You can exchange an existing `JWT` with a new one that will be signed with the secret of the target API. This is typically used for identity delegation (i.e. user logged in to an application with a token signed for that application, then he calls an API which is protected with a different secret). Look under **SDK - Authentication API - Delegation** (on the dashboard). + +> By default, all your clients will be allowed to make delegation requests. If you want to specify only some of them, please go to Apps / APIs Settings - Allowed Applications / APIs (on the dashboard). + +Once you get the token, you can call the API attaching the `JWT` on the `Authorization` header. + + Authorization: Bearer ...JWT... diff --git a/docs/includes/callback.md b/docs/includes/callback.md new file mode 100644 index 00000000..dc07026e --- /dev/null +++ b/docs/includes/callback.md @@ -0,0 +1,13 @@ +### Before Starting + +
      +<% if (account.userName && hasCallback) { %> +

      Please remember that for security purposes, you have to register the URL of your app on the Settings Section section on Auth0 Admin app as the callbackURL.

      +

      Right now, that callback is set to the following: +

      <%= account.callback %>
      +

      +<% } else { %> +

      Please remember that for security purposes, you have to register the URL of your app on the Settings Section section on Auth0 Admin app as the callbackURL.

      +<% } %> + +
      diff --git a/docs/includes/callbackRegularWebapp.md b/docs/includes/callbackRegularWebapp.md new file mode 100644 index 00000000..4492173d --- /dev/null +++ b/docs/includes/callbackRegularWebapp.md @@ -0,0 +1,11 @@ +
      +<% if (account.userName && hasCallback) { %> +

      Please remember that for security purposes, you have to register the callback URL of your app on the Application Settings section on Auth0 Admin app.

      +

      Right now, that callback is set to the following: +

      <%= account.callback %>
      +

      +<% } else { %> +

      Please remember that for security purposes, you have to register the callback URL of your app on the Application Settings section on Auth0 Admin app.

      +<% } %> + +
      diff --git a/docs/includes/integrations.md b/docs/includes/integrations.md new file mode 100644 index 00000000..9bfd5186 --- /dev/null +++ b/docs/includes/integrations.md @@ -0,0 +1,23 @@ +## <%= integration.name %> integration + +In this tutorial, you'll learn how to add Single Sign On (SSO) to <%= integration.name %> using Auth0. Your users will be able to log in using any of our [Social Identity Providers](@@base_url@@/identityproviders) (Facebook, Twitter, Github, etc.), [Enterprise Providers](@@base_url@@/identityproviders) (LDAP, Active Directory, ADFS, etc.) or with username and password. + +### 1. Adding the Integration to your account + +The first thing you need to do is go to the [Third party apps](https://app.auth0.com/#/externalapps/create) section in the dashboard and choose <%= integration.name %> from the list of apps. + +![Choose the new app](https://cloudup.com/cs5kEnkFs9i+) + +### 2. Follow the live documentation + +Now, you should see a tutorial. This tutorial has all the information tailored for your account. It might ask you to enter information from <%= integration.name %>. Just follow each of the steps shown. + +![Live doc](https://cloudup.com/iOFJHDb5M4O+) + +### 3. You've nailed it. + +You have configured <%= integration.name %> to use Auth0 as the SSO Broker. Congrats, you're awesome! + + + + diff --git a/docs/includes/thirdpartyapi.md b/docs/includes/thirdpartyapi.md new file mode 100644 index 00000000..6b729ebe --- /dev/null +++ b/docs/includes/thirdpartyapi.md @@ -0,0 +1,19 @@ +# <%= _.capitalize(configuration.thirdParty) %> API + +Please follow the steps below to configure your Auth0 account to work with <%= _.capitalize(configuration.thirdParty) %>. + +### 1. Activate the add-on. + +Go to Application Add-ons page and activate the <%= _.capitalize(configuration.thirdParty) %> add-on. + + + +Each integration is different and requires different parameters and configuration. once the add-on is activated, you will see tailored instructions with details on how to get this done. + +### 2. Use it + +The key to this integration is the Delegation endpoint in Auth0. Check the documentation of any of our FrontEnd or Mobile SDKs to learn how to call the [this endpoint](https://docs.auth0.com/auth-api#delegated). You can download your favorite library from any of the [Quickstarts](https://docs.auth0.com/). + +### 3. You are done! + +Congrats! You've implemented Delegation for the <%= _.capitalize(configuration.thirdParty) %> API diff --git a/docs/index.md b/docs/index.md index 258ceb67..04cdd8f1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,34 +1,227 @@ --- -title: Auth0 | Getting started with Auth0 +title: Getting started with Auth0 url: / --- + + + + + # Getting started with Auth0 -## First things first +Hello@@account.userName ? ' ' + account.userName : ''@@! Ready to test drive Auth0? All tutorials have been tailored to your account. So in most cases, all you need to do is copy the code snippets and paste them into your projects and tools. + + + +
      + +
      + + + + + + -- [Chat with us!](http://chat.auth0.com) -- [Contact us!](mailto:support@auth0.com) -- [Visit GitHub for samples on various platforms](https://github.com/auth0) diff --git a/docs/instagram-clientid.md b/docs/instagram-clientid.md new file mode 100644 index 00000000..fb4593d5 --- /dev/null +++ b/docs/instagram-clientid.md @@ -0,0 +1,21 @@ +# Obtaining a Client ID and Client Secret for Instagram + +To configure Instagram OAuth2 connections you will need to register Auth0 with Instagram on their [developer portal](http://instagram.com/developer). + +##1. Log in into the developer portal +Go to the [developer portal](http://instagram.com/developer), and log in with your Instagram credentials. Click on __Register Your Application__: + +![](//cdn.auth0.com/docs/img/instagram-devportal-1.png) + +--- + +##2. Complete information about your instance of Auth0 + +Create a new application and complete the form. Use this URL as your callback: + + https://@@account.namespace@@/login/callback + +![](//cdn.auth0.com/docs/img/instagram-devportal-2.png) + +Enter your new `Client ID` and `Client Secret` into the connection settings in Auth0. + diff --git a/docs/integrations/ad-rms.md b/docs/integrations/ad-rms.md new file mode 100644 index 00000000..6519c80b --- /dev/null +++ b/docs/integrations/ad-rms.md @@ -0,0 +1,7 @@ +--- +title: AD RMS +lodash: true +--- +<% integration = {}; +integration.name = "AD RMS"; %> +@@includes.integrations@@ diff --git a/docs/integrations/box.md b/docs/integrations/box.md new file mode 100644 index 00000000..caf85057 --- /dev/null +++ b/docs/integrations/box.md @@ -0,0 +1,7 @@ +--- +title: Box +lodash: true +--- +<% integration = {}; +integration.name = "Box"; %> +@@includes.integrations@@ diff --git a/docs/integrations/cloudbees.md b/docs/integrations/cloudbees.md new file mode 100644 index 00000000..0ef3844c --- /dev/null +++ b/docs/integrations/cloudbees.md @@ -0,0 +1,7 @@ +--- +title: CloudBees +lodash: true +--- +<% integration = {}; +integration.name = "CloudBees"; %> +@@includes.integrations@@ diff --git a/docs/integrations/concur.md b/docs/integrations/concur.md new file mode 100644 index 00000000..50211e60 --- /dev/null +++ b/docs/integrations/concur.md @@ -0,0 +1,7 @@ +--- +title: Concur +lodash: true +--- +<% integration = {}; +integration.name = "Concur"; %> +@@includes.integrations@@ diff --git a/docs/integrations/dropbox.md b/docs/integrations/dropbox.md new file mode 100644 index 00000000..adfa94ab --- /dev/null +++ b/docs/integrations/dropbox.md @@ -0,0 +1,7 @@ +--- +title: Dropbox +lodash: true +--- +<% integration = {}; +integration.name = "Dropbox"; %> +@@includes.integrations@@ diff --git a/docs/integrations/dynamic-crm.md b/docs/integrations/dynamic-crm.md new file mode 100644 index 00000000..fc059e78 --- /dev/null +++ b/docs/integrations/dynamic-crm.md @@ -0,0 +1,7 @@ +--- +title: Dynamic CRM +lodash: true +--- +<% integration = {}; +integration.name = "Dynamic CRM"; %> +@@includes.integrations@@ diff --git a/docs/integrations/echosign.md b/docs/integrations/echosign.md new file mode 100644 index 00000000..b20ed021 --- /dev/null +++ b/docs/integrations/echosign.md @@ -0,0 +1,7 @@ +--- +title: EchoSign +lodash: true +--- +<% integration = {}; +integration.name = "EchoSign"; %> +@@includes.integrations@@ diff --git a/docs/integrations/egnyte.md b/docs/integrations/egnyte.md new file mode 100644 index 00000000..88943c83 --- /dev/null +++ b/docs/integrations/egnyte.md @@ -0,0 +1,7 @@ +--- +title: Egnyte +lodash: true +--- +<% integration = {}; +integration.name = "Egnyte"; %> +@@includes.integrations@@ diff --git a/docs/integrations/new-relic.md b/docs/integrations/new-relic.md new file mode 100644 index 00000000..2dc967f4 --- /dev/null +++ b/docs/integrations/new-relic.md @@ -0,0 +1,7 @@ +--- +title: New Relic +lodash: true +--- +<% integration = {}; +integration.name = "New Relic"; %> +@@includes.integrations@@ diff --git a/docs/integrations/office-365.md b/docs/integrations/office-365.md new file mode 100644 index 00000000..fbe3619e --- /dev/null +++ b/docs/integrations/office-365.md @@ -0,0 +1,7 @@ +--- +title: Office 365 +lodash: true +--- +<% integration = {}; +integration.name = "Office 365"; %> +@@includes.integrations@@ diff --git a/docs/integrations/salesforce.md b/docs/integrations/salesforce.md new file mode 100644 index 00000000..34149c3c --- /dev/null +++ b/docs/integrations/salesforce.md @@ -0,0 +1,7 @@ +--- +title: Salesforce +lodash: true +--- +<% integration = {}; +integration.name = "Salesforce"; %> +@@includes.integrations@@ diff --git a/docs/integrations/sharepoint.md b/docs/integrations/sharepoint.md new file mode 100644 index 00000000..e0329d4d --- /dev/null +++ b/docs/integrations/sharepoint.md @@ -0,0 +1,7 @@ +--- +title: Sharepoint +lodash: true +--- +<% integration = {}; +integration.name = "Sharepoint"; %> +@@includes.integrations@@ diff --git a/docs/integrations/springcm.md b/docs/integrations/springcm.md new file mode 100644 index 00000000..2eb544bf --- /dev/null +++ b/docs/integrations/springcm.md @@ -0,0 +1,7 @@ +--- +title: Spring CM +lodash: true +--- +<% integration = {}; +integration.name = "SpringCM"; %> +@@includes.integrations@@ diff --git a/docs/integrations/zendesk.md b/docs/integrations/zendesk.md new file mode 100644 index 00000000..492659d9 --- /dev/null +++ b/docs/integrations/zendesk.md @@ -0,0 +1,7 @@ +--- +title: ZenDesk +lodash: true +--- +<% integration = {}; +integration.name = "ZenDesk"; %> +@@includes.integrations@@ diff --git a/docs/integrations/zoom.md b/docs/integrations/zoom.md new file mode 100644 index 00000000..6aa51c87 --- /dev/null +++ b/docs/integrations/zoom.md @@ -0,0 +1,7 @@ +--- +title: Zoom +lodash: true +--- +<% integration = {}; +integration.name = "Zoom"; %> +@@includes.integrations@@ diff --git a/docs/invite-only.md b/docs/invite-only.md new file mode 100644 index 00000000..5b25ebee --- /dev/null +++ b/docs/invite-only.md @@ -0,0 +1,293 @@ +# Invite-only applications + +Self-service provisioning is a common concept for SaaS applications, where users can register and pay after which they can start using the application. Other types of applications might not allow single users to register for an application. Instead, the customers might be organizations that pay upfront for a number of users and only want those users to access your application. Think about platforms like Google Apps and Office 365. + +And this is where an invite-only workflow can be used. Let’s take a look at a fictitious application, Analystick, which is a multi-tenant SaaS solution offering analytics in the cloud. Their customers will send them a list of users (give name, family name and email address) that can access the application. + +As always, this is simply one way to solve it. Another option to achieve this is by using an Enterprise Connection, where you can federate with your customer using ADFS/SAML-P/… allowing them to authenticate using their own Active Directory (in which they could then specify who can access the application). + +Here's how we're going to setup the invite only flow. The tenant admnistrator will be able to create new users in his subscription from within the application (1). The application will call the Auth0 API to create the new users in a database connection (2) and will send out activation emails for all users (3). When a user clicks the activation link he'll be redirected to Auth0 (4) where his email address will be set to validated. After validation Auth0 will redirect the user to the application and be presented with a password reset form (5). Finally the application will update the user's password in Auth0 after which the user will be able to authenticate. + +![](//cdn.auth0.com/docs/img/invite-only-overview.png) + +## Setup + +The users will be stored in a database and this is why we’ll need to make sure that we have a database connection available. We’ll only need a single database because Analystick will sign up users of Contoso, Fabrikam and other companies with their corporate email address (making users unique for each customer). + +![](//cdn.auth0.com/docs/img/invite-only-connections.png) + +The Analystick application is an ASP.NET MVC web application hosted on http://localhost:45000/, so we’ll need to make sure we create an application in the dashboard with the right parameters: + + - **Name**: give your application a clear name as this will be used in the emails being sent out during the invite-only workflow + - **Allowed Callback URLs**: this should be the url of your application followed with /signin-auth0 (a requirement of the Auth0.Owin NuGet package for .NET) + +![](//cdn.auth0.com/docs/img/invite-only-app.png) + +## User Management + +The team at Analystick then decided to build a simple user interface in their admin backend allowing the import of users. This UI could potentially allow the upload of CSV, XML, JSON … files but for simplicity we’ll stick to a page that allows you to create up to 5 users. + +![](//cdn.auth0.com/docs/img/invite-only-new.png) + +This admin interface simply uses the Auth0 SDK for .NET to communicate with the Auth0 API: + +``` +public class UsersController : Controller +{ + private readonly Client _client; + + public UsersController() + { + _client = new Client( + ConfigurationManager.AppSettings["auth0:ClientId"], + ConfigurationManager.AppSettings["auth0:ClientSecret"], + ConfigurationManager.AppSettings["auth0:Domain"]); + } + + public ActionResult Index() + { + var users = _client.GetUsersByConnection(ConfigurationManager.AppSettings["auth0:Connection"]); + return View(users.Select(u => new UserModel + { + UserId = u.UserId, + GivenName = u.GivenName, + FamilyName = u.FamilyName, + Email = u.Email + }).ToList()); + } + + ... + + [HttpPost] + public ActionResult New(IEnumerable users) + { + if (users != null) + { + foreach (var user in users.Where(u => !String.IsNullOrEmpty(u.Email))) + { + var randomPassword = Guid.NewGuid().ToString(); + var metadata = new + { + user.GivenName, + user.FamilyName, + activation_pending = true + }; + + var profile = _client.CreateUser(user.Email, randomPassword, + ConfigurationManager.AppSettings["auth0:Connection"], false, metadata); + + var userToken = JWT.JsonWebToken.Encode( + new { id = profile.UserId, email = profile.Email }, + ConfigurationManager.AppSettings["analystick:signingKey"], + JwtHashAlgorithm.HS256); + + var verificationUrl = _client.GenerateVerificationTicket(profile.UserId, + Url.Action("Activate", "Account", new { area = "", userToken }, Request.Url.Scheme)); + + var body = "Hello {0}, " + + "Great that you're using our application. " + + "Please click ACTIVATE to activate your account." + + "The Analystick team!"; + + var fullName = String.Format("{0} {1}", user.GivenName, user.FamilyName).Trim(); + var mail = new MailMessage("app@auth0.com", user.Email, "Hello there!", + String.Format(body, fullName, verificationUrl)); + mail.IsBodyHtml = true; + + var mailClient = new SmtpClient(); + mailClient.Send(mail); + } + } + + return RedirectToAction("Index"); + } + + [HttpPost] + public ActionResult Delete(string id) + { + if (!String.IsNullOrEmpty(id)) + _client.DeleteUser(id); + return RedirectToAction("Index"); + } +} +``` + +If you take a closer look at the code you’ll see that the CreateUser method is called to create the user in the database connection. This method is being called with 4 parameters: + + 1. The user’s email address + 2. The user’s password (we’re generating a new Guid to assign a random password to the user) + 3. The name of the connection in which we want to create the user + 4. The email verified parameter (we're setting this to false because we need the user to click the activation link). + 5. A metadata object containing a the given name and family name of the user, together with an activation pending setting (which we’ll use later to validate the user). + +## Emails ## + +Once the user is created we'll need to send out the email verification email. The most important part here is generating the email verification url. Our goal is to send the user to the password reset form in the application (/Activate/Account), but we need a secure way to identify the user. This is why we're generating a token that identifies the user (we use a JWT token for that) and append this to the account activation url. Finally we're calling the ```GenerateVerificationTicket``` method on the SDK to generate the Auth0 verification url and we set the return url to the url of our password reset form (with token). + +Since we don’t want the default emails to be sent out we’ll need to go to the dashboard and disable the **Verification Email** and **Welcome Email**. + +![](//cdn.auth0.com/docs/img/invite-only-disable-email.png) + +Since our backend will be sending out the email we’ll need access to an SMTP server. For testing purposes we’re using Mailtrap, but any SMTP server will do. After signing up we’re adding the SMTP settings to the web.config: + +```xml + + + + + + + +``` + +And that's it for the user provisioning. If we go back to the user overview we can start importing a few users. + +![](//cdn.auth0.com/docs/img/invite-only-users.png) + +Each user will now also have received an email welcoming them and giving them a chance to activate their account. + +![](//cdn.auth0.com/docs/img/invite-only-activation-mail.png) + +## User Activation ## + +The link in our email template will redirect to Auth0 for email verification, after which Auth0 will redirect the user to the password reset form in the application (see how the user token is added to the url). + +![](//cdn.auth0.com/docs/img/invite-only-activation.png) + +Once the user entered his password we'll verify that the account hasn't been updated yet, we'll update the user's password and mark him as active (```activation_pending = false```). + +```csharp +/// +/// GET Account/Activate?userToken=xxx +/// +/// +/// +public ActionResult Activate(string userToken) +{ + dynamic metadata = JWT.JsonWebToken.DecodeToObject(userToken, + ConfigurationManager.AppSettings["analystick:signingKey"]); + var user = GetUserProfile(metadata["id"]); + if (user != null) + return View(new UserActivationModel { Email = user.Email, UserToken = userToken }); + return View("ActivationError", + new UserActivationErrorModel("Error activating user, could not find an exact match for this email address.")); +} + +/// +/// POST Account/Activate +/// +/// +/// +[HttpPost] +public ActionResult Activate(UserActivationModel model) +{ + dynamic metadata = JWT.JsonWebToken.DecodeToObject(model.UserToken, + ConfigurationManager.AppSettings["analystick:signingKey"], true); + if (metadata == null) + { + return View("ActivationError", + new UserActivationErrorModel("Unable to find the token.")); + } + + if (!ModelState.IsValid) + { + return View(model); + } + + UserProfile user = GetUserProfile(metadata["id"]); + if (user != null) + { + if (user.ExtraProperties.ContainsKey("activation_pending") + && !((bool)user.ExtraProperties["activation_pending"])) + return View("ActivationError", + new UserActivationErrorModel("Error activating user, the user is already active.")); + + _client.ChangePassword(user.UserId, model.Password, false); + _client.UpdateUserMetadata(user.UserId, new { activation_pending = false }); + + return View("Activated"); + } + + return View("ActivationError", + new UserActivationErrorModel("Error activating user, could not find an exact match for this email address.")); +} +``` + +Note that we always validate the token first before proceeding, to make sure we’re making the change for the right person. + +As a final step we're showing a confirmation page where the user can click a link to sign in. One last customization we want to apply is the rendering of the Lock. Since we don’t want users to sign up we’re going to hide the Sign Up button (which is visible by default): + +```javascript +var lock = new Auth0Lock('@System.Configuration.ConfigurationManager.AppSettings["auth0:ClientId"]', '@System.Configuration.ConfigurationManager.AppSettings["auth0:Domain"]'); + +function showLock() { + lock.show({ + callbackURL: window.location.origin + '/signin-auth0', + disableSignupAction: true + }); +} +``` + +![](//cdn.auth0.com/docs/img/invite-only-login.png) + +As a final step we’re also enforcing the user activation. When we configure Auth0 at application startup we can intercept every login, allowing us to modify the user’s identity before handing it over to the OWIN pipeline. + +In this example we’re checking if a user is active, and if that’s the case we’ll add the "Member" role to the user: + +```csharp +public partial class Startup +{ + public void ConfigureAuth(IAppBuilder app) + { + ... + + var provider = new Auth0AuthenticationProvider + { + OnReturnEndpoint = context => + { + .. + }, + OnAuthenticated = context => + { + if (context.User["activation_pending"] != null) + { + var pending = context.User.Value("activation_pending"); + if (!pending) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Role, "Member")); + } + } + + ... + + return Task.FromResult(0); + } + }; + + app.UseAuth0Authentication(ConfigurationManager.AppSettings["auth0:ClientId"], + ConfigurationManager.AppSettings["auth0:ClientSecret"], ConfigurationManager.AppSettings["auth0:Domain"], + provider: provider); + } +} +``` + +And now we can protect our pages which should only be accessible to users by enforcing the presence of a Member claim: + +```csharp +[Authorize(Roles = "Member")] +public class ProfileController : Controller +{ + public ActionResult Index() + { + ... + } +} +``` + +## Summary ## + +Once the user has gone through the whole flow he'll be able to access the member-only pages. + +![](//cdn.auth0.com/docs/img/invite-only-profile.png) + +This scenario covered how to implement an invite-only flow by using Auth0 API to completely customize the signup process and the email flow. For more information about the API you can use the [API explorer](api) and [API v2 explorer](apiv2) to try the different endpoints. diff --git a/docs/ios-tutorial.md b/docs/ios-tutorial.md index 4f763a6c..f16dc834 100644 --- a/docs/ios-tutorial.md +++ b/docs/ios-tutorial.md @@ -1,29 +1,39 @@ # Using Auth0 with iOS -This tutorial explains how to integrate Auth0 with a iOS App. `iAuth0Client` helps you authenticate users with any [Auth0 supported identity provider](identityproviders). +This tutorial explains how to integrate Auth0 with a iOS App. `Auth0Client` helps you authenticate users with any [Auth0 supported identity provider](identityproviders). ## Tutorial -### 1. Install iAuth0Client static library +### 1. Add Auth0Client library to your project -1. Go to [Auth0.iOS repository in Github](https://github.com/auth0/Auth0.iOS) and click on __Download ZIP__ -2. Decompress it and reference the `iAuth0Client` static library to your project: + +#### CocoaPods +Add the following line to your _Podfile_ +```ruby +pod 'Auth0Client' +``` +You can check our latest version in [Auth0.iOS Github Releases](https://github.com/auth0/Auth0.iOS/releases). + +#### Install Auth0Client library in your project + +1. Go to [Auth0.iOS repository in GitHub](https://github.com/auth0/Auth0.iOS) and click on __Download ZIP__ +2. Decompress it and reference the `Auth0Client` library in your project: * Go to your project in XCode * Right-click on the `Frameworks` folder and select ___Add Files to "Your Project Name"___ - * Select the `iAuth0Client` folder, ensure that your project target is selected and press __Add__ + * Select the `Auth0Client` folder, ensure that your project target is selected and press __Add__ ### 2. Setting up the callback URL in Auth0
      -

      Go to the Application Settings section on Auth0 Admin app and make sure that App Callbacks URLs has the following value:

      +

      Go to the Application Settings section in the Auth0 dashboard and make sure that Allowed Callback URLs contains the following value:

      https://@@account.namespace@@/mobile
      ### 3. Integration -There are three options to do the integration: +There are three options to do the integration: -1. Using the [Auth0 Login Widget](login-widget) inside a Web View (this is the simplest with only a few lines of code required). +1. Using the [Auth0Lock](lock) inside a Web View (this is the simplest with only a few lines of code required). 2. Creating your own UI (more work, but higher control the UI and overall experience). 3. Using specific user name and password. @@ -34,34 +44,33 @@ To start with, we'd recommend using the __Login Widget__. Here is a snippet of c ```objective-c #import "Auth0Client.h" -Auth0Client *client = [Auth0Client auth0Client:@"@@account.tenant@@" - clientId:@"@@account.clientId@@" - clientSecret:@"@@account.clientSecret@@"]; +Auth0Client *client = [Auth0Client auth0Client:@"@@account.namespace@@" + clientId:@"@@account.clientId@@"]; -[client loginAsync:self withCompletionHandler:^(BOOL authenticated) { - if (!authenticated) { - NSLog(@"Error authenticating"); +[client loginAsync:self withCompletionHandler:^(NSMutableDictionary* error) { + if (error) { + NSLog(@"Error authenticating: %@", [error objectForKey:@"error"]); } - else{ + else { // * Use client.auth0User to do wonderful things, e.g.: // - get user email => [client.auth0User.Profile objectForKey:@"email"] // - get facebook/google/twitter/etc access token => [[[client.auth0User.Profile objectForKey:@"identities"] objectAtIndex:0] objectForKey:@"access_token"] - // - get Windows Azure AD groups => [client.auth0User.Profile objectForKey:@"groups"] + // - get Microsoft Azure AD groups => [client.auth0User.Profile objectForKey:@"groups"] // - etc. } }]; ``` -![](img/iOS-step1.png) +![](//cdn.auth0.com/docs/img/iOS-step1.png) #### Option 2: Authentication with your own UI -If you know which identity provider you want to use, you can add a `connection` parameter to the constructor and the user will be sent straight to the specified `connection`: +If you know which identity provider you want to use, you can add a `connection` parameter and the user will be sent straight to the specified `connection`: ```objective-c -[client loginAsync:self connection:@"auth0waadtests.onmicrosoft.com" withCompletionHandler:^(BOOL authenticated) -{ - /* Use client.auth0User to do wonderful things */ +[client loginAsync:self connection:@"auth0waadtests.onmicrosoft.com" withCompletionHandler:^(NSMutableDictionary* error) +{ + /* Use client.auth0User to do wonderful things */ }]; ``` @@ -70,12 +79,17 @@ If you know which identity provider you want to use, you can add a `connection` #### Option 3: Authentication with specific user name and password (only for providers that support this) ```objective-c -[client loginAsync:self connection:@"my-db-connection" +[client loginAsync:self connection:@"my-db-connection" username:@"username" password:@"password" - withCompletionHandler:^(BOOL authenticated) -{ - /* Use client.auth0User to do wonderful things */ + withCompletionHandler:^(NSMutableDictionary* error) +{ + if (error) { + NSLog(@"Error authenticating: %@ - %@", [error objectForKey:@"error"], [error objectForKey:@"error_description"]); + } + else { + /* Use client.auth0User to do wonderful things */ + } }]; ``` @@ -84,9 +98,8 @@ If you know which identity provider you want to use, you can add a `connection` The `auth0User` has the following properties: * `Profile`: returns a `NSDictionary` object containing all available user attributes (e.g.: `[client.auth0User.Profile objectForKey:@"email"]`). -* `IdToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity (or Windows Azure Mobile Services, see below). +* `IdToken`: is a Json Web Token (JWT) containing the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity. * `Auth0AccessToken`: the `access_token` that can be used to access Auth0's API. You would use this for example to [link user accounts](link-accounts). -> If you want to use __Windows Azure Mobile Services__ (WAMS) you should create a WAMS app in Auth0 and set the Master Key that you can get on the Windows Azure portal. Then you have change your iOS App to use the client id and secret of the WAMS app just created and set the callback of the WAMS app to be `https://@@account.tenant@@.auth0.com/mobile`. Finally, you have to set the `MobileServiceAuthenticationToken` property of the `MobileServiceUser` with the `IdToken` property of `auth0User`. -**Congratulations!** \ No newline at end of file +**Congratulations!** diff --git a/docs/java-tutorial.md b/docs/java-tutorial.md new file mode 100644 index 00000000..4d1df8f5 --- /dev/null +++ b/docs/java-tutorial.md @@ -0,0 +1,270 @@ +# Auth0 and Java + +[Auth0](https://www.auth0.com) is a cloud service that provides a turn-key solution for authentication, authorization and Single Sign On. + +You can use [Auth0](https://www.auth0.com) to add username/password authentication, support for enterprise identity like Active Directory or SAML and also for social identities like Google, Facebook or Salesforce among others to your web, API and mobile native apps. + +## Servlet-based Application Tutorial + +This guide will walk you through adding authentication to an existing Java Web Application that is based on [Java Servlet Technology](http://www.oracle.com/technetwork/java/index-jsp-135475.html). + +In case you are starting from scratch, you can create a Java Web Application by using the `maven-archetype-webapp` + +``` +mvn archetype:generate -DgroupId=com.acme \ + -DartifactId=my-webapp \ + -Dversion=1.0-SNAPSHOT \ + -DarchetypeArtifactId=maven-archetype-webapp \ + -DinteractiveMode=false + +``` + +### 1. Adding the Auth0 Maven Dependency + +Let's start by adding the Auth0-servlet artifact to the project `pom.xml` file. + +```xml + + com.auth0 + auth0-servlet + 1.4 + +``` + +> Note: if you are not using Maven you can download the jar from [here](https://github.com/auth0/auth0-java/releases). + +### 2. Setting up the callback URL in Auth0 + +After authenticating the user on Auth0, we will do a POST to a URL on your web site. For security purposes, you have to register this URL on the Application Settings section on Auth0 Admin app. + +``` +http://localhost:PORT/callback +``` + +### 3. Filtering Requests + +Let's start configuring the `web.xml` found in your Web Application: + +```xml + + ... + + ... + + Protected + com.auth0.example.HelloServlet + + + RedirectCallback + com.auth0.Auth0ServletCallback + + auth0.redirect_on_success + /protected + + + auth0.redirect_on_error + /login + + + + Login + com.auth0.example.LoginServlet + + + + ... + + Protected + /protected/* + + + Login + /login + + + Login + / + + + RedirectCallback + /callback + + + + ... + + AuthFilter + com.auth0.Auth0Filter + + auth0.redirect_on_authentication_error + /login + + + + AuthFilter + /protected/* + + + + ... + + auth0.client_id + YOUR_CLIENT_ID + + + + auth0.client_secret + YOUR_CLIENT_SECRET + + + + auth0.domain + your-domain.auth0.com + + + +``` + +In the `Auth0ServletCallback` the data to popuplate principal will be persisted in session. As we will see later this can be customized. + +As configured previously, the user will be redirected to `/protected`. User-provided `HelloServlet`, which overrides `doGet` method, will be handling that case. + +### 4. (Optional) Auth0Lock + +@@lockSDK@@ + +### 5. (Optional) Customize your JSP login page + +First, we are going to create a Servlet to handle the login page: + +```java +protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // import com.auth0.RequestNonceStorage; + RequestNonceStorage nonceStorage = new RequestNonceStorage(request); + if (!"/favicon.ico".equals(request.getServletPath())) { + // import com.auth0.NonceGenerator; + String nonce = nonceGenerator.generateNonce(); + nonceStorage.setState(nonce); + request.setAttribute("state", nonce); + request.getRequestDispatcher("/login.jsp").forward(request, response); + } else { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } +} +``` + +Next step is to add a Login Page (`/login.jsp`) with the custom widget of the previous section. We are going to get some of the widget configuration data from the `web.xml`. + +```jsp + + + + + Login + + + + + <% if ( request.getParameter("error") != null ) { %> + <%-- TODO Escape and encode ${param.error} properly. It can be done using jstl c:out. --%> + ${param.error} + <% } %> + + + +``` + +Point your browser to `/login` and you will be seeing that login page. + +### 6. Extensibility points + +On the first part, we explained how to get running up and fast with Auth0 in your app. But, probably, you needed some degree of customization over any of the involved parts. We will see how to customize it to better suit your needs. + +In order to handle the callback call from Auth0, you will need to have a Servlet that handles the request. + +#### Auth0 Filter + +`Auth0 Filter` can be subclassed and the following `protected` methods meant to be overriden: + + * onSuccess: What should be done when the user is authenticated. + * onReject: What should be done when the user is not authenticated. + * loadTokens: You should override this method to provide a custom way of restoring both id and access tokens. By default, they are stored in the Session object but, for instance, they can be persisted in databases. + +Method signatures are as follows: + +```java +protected void onSuccess( + ServletRequest req, ServletResponse resp, FilterChain next, Tokens tokens) + throws IOException, ServletException { + +} + +protected void onReject( + ServletRequest req, ServletResponse response, FilterChain next) + throws IOException, ServletException { + +} + +protected Tokens loadTokens(ServletRequest req, ServletResponse resp) { + +} +``` + +#### Auth0Principal + +`Auth0Principal` class can be subclassed in order to expose custom information you want your principal to have. + +#### Auth0ServletCallback + +`Auth0ServletCallback` methods `saveTokens` and `onSuccess` can be both overriden. One, to provide the way to store accessTokens. The other, to override the onSuccess behaviour (when user is authenticated). + +Method signatures are: + +```java +protected void saveTokens( + HttpServletRequest req, HttpServletResponse resp, Tokens tokens) + throws ServletException, IOException { +} + +protected void onSuccess( + HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { +} +``` + diff --git a/docs/jwt.md b/docs/jwt.md index 155116ad..9f61a660 100644 --- a/docs/jwt.md +++ b/docs/jwt.md @@ -37,7 +37,7 @@ The minimum information will be: * `exp` the __expiration__, set to 10 hours. * `iat` the __issued at timestamp__. -If the `scope` in the authorization request is set to `scope=openid profile`, then all the properties of the [user profile](user-profile) are added to the Body. +If the `scope` in the authorization request is set to `scope=openid profile`, then all the properties of the [user profile](user-profile) are added to the Body. You can also define specific attributes with the syntax: `scope: 'openid {attr1} {attr2} {attrN}'`. For example: `scope: 'openid name email picture'`. > __Beware!__ If you are using the `implicit flow`, as you would if you are issuing the authorization request from a device, the JWT is returned in the URL, not in the response body. Some browsers have restrictions on URL lengths and can give you unexpected results. @@ -58,8 +58,8 @@ Where: --- -##When using Windows Azure Mobile Services -Windows Azure Mobile Services (WAMS) APIs expects a specific format of JWTs. WAMS compatible JWT have an additional property in the body: +##When using Microsoft Azure Mobile Services +Microsoft Azure Mobile Services (WAMS) APIs expects a specific format of JWTs. WAMS compatible JWT have an additional property in the body: uid: "{connection}|{user_id}" diff --git a/docs/lifecycle.md b/docs/lifecycle.md new file mode 100644 index 00000000..92c3bbce --- /dev/null +++ b/docs/lifecycle.md @@ -0,0 +1,31 @@ +# Development Lifecycle in Auth0 + +__Development__, __Test__, __Q&A__ environments are easy to create in Auth0. You simply create a new account for each. This guarantees maximum isolation between them. You can easily switch between accounts using the account chooser on the top right menu on the dashboard. You can also have different administrative accounts in each. + +![](https://docs.google.com/drawings/d/1ceFEtCtIvZz_0J7ugDgxAMAv4YPIIYKOmfa4lzFbQDo/pub?w=607&h=298) + +The example above uses a simple naming convention to distinguish one environment from the other. + +Through the API you can automate migrating assets between one account and the other (e.g. rules, database connections, etc.). + +For easier configuration management we recommend you use settings kept in the dashboard as opposed to hardcoded in your __rules__ or __db connections__ scripts. + +For example, in this __rule__ it is always better to write: + +``` +function(user, context, callback){ + var log_url = configuration.log_url; +... +} +``` + +than: + +``` +function(user, context, callback){ + var log_url = ‘https://someurl/log’; +... +} +``` + +Very likely this URL will change from development to production. This will make your code more portable. diff --git a/docs/link-accounts.md b/docs/link-accounts.md index 67bef936..bafc2d92 100644 --- a/docs/link-accounts.md +++ b/docs/link-accounts.md @@ -1,15 +1,33 @@ # Linking Accounts -Auth0 supports the association of different accounts. Applications often support multiple identity providers. Through linking, a user can authenticate with one identity provider and later on with another, but appear to the app as being the same. +Auth0 supports the association of different accounts. Applications often support multiple identity providers. Through linking, a user can authenticate with one identity provider and later on with another, but appear to the app as being the same. **Linking through Auth0 Login Widget** ``` - -Add account + + + +Add account ``` -> Notice the `access_token` fragment of the URL that is normally not present. This is the `access_token` Auth0 will generate when a user logs in. It identifies a logged in user univocally in Auth0. +> Notice the `access_token` fragment of the URL that is normally not present. This is the `access_token` Auth0 will generate when a user logs in. It identifies a logged in user uniquely in Auth0. **Manually initiating the authentication transaction** @@ -60,10 +78,10 @@ The SDKs should make this very easy. The SDK for your platform will make it avai <%= ClaimsPrincipal.Current.FindFirst("access_token").Value %> ``` -If you are rolling up your own implementation, it will be available through the standard OAuth2 flow: +If you are rolling your own implementation, it will be available through the standard OAuth2 flow: -1- User logs in and returns to the app with a `code` -2- The app exchanges the `code` for the `access_token` + 1. User logs in and returns to the app with a `code` + 2. The app exchanges the `code` for the `access_token` The details of these exchanges are available in the [protocols section](protocols). @@ -71,17 +89,17 @@ The details of these exchanges are available in the [protocols section](protocol To unlink a specific account, POST request to the following url: -`https://@@account.namespace@@/unlink?` +`https://@@account.namespace@@/unlink` Body should be: ``` { - clientID: @@account.clientId@@, - access_token: LOGGED_IN_USER_ACCESS_TOKEN, // Primary identity access_token - user_id: LINKED_USER_ID // (provider|id) + clientID: "@@account.globalClientId@@", // Your global client ID + access_token: "LOGGED_IN_USER_ACCESS_TOKEN", // Primary identity access_token + user_id: "LINKED_USER_ID" // (provider|id) } ``` -Using the sample `User Profile` above, to __unlink__ the Windows Live Id identity, you would send, `user_id: 'windowlive|9876543210987654321'`. +Using the sample `User Profile` above, to __unlink__ the Windows Live Id identity, you would send, `user_id: 'windowslive|9876543210987654321'`. diff --git a/docs/linkedin-clientid.md b/docs/linkedin-clientid.md index 9d48d8ae..c30bde0f 100644 --- a/docs/linkedin-clientid.md +++ b/docs/linkedin-clientid.md @@ -1,15 +1,15 @@ -# Obtaining an API Key and Secret Key for LinkedId +# Obtaining an API Key and Secret Key for LinkedIn To configure LinkedIn OAuth2 connections you will need to register Auth0 with LinkedIn on their [developer portal](http://developer.linkedin.com/). ##1. Log in into the developer portal Go to the [developer portal](http://developer.linkedin.com/) and login with your LinkedIn credentials: -![](img/linkedin-devportal-1.png) +![](//cdn.auth0.com/docs/img/linkedin-devportal-1.png) Then select __API Keys__ under the support menu option: -![](img/linkedin-devportal-2.png) +![](//cdn.auth0.com/docs/img/linkedin-devportal-2.png) --- @@ -17,7 +17,7 @@ Then select __API Keys__ under the support menu option: Create a new application and complete the form: -![](img/linkedin-devportal-3.png) +![](//cdn.auth0.com/docs/img/linkedin-devportal-3.png) --- @@ -25,9 +25,7 @@ Create a new application and complete the form: https://@@account.namespace@@/login/callback -![](img/linkedin-devportal-4.png) - -> This step is optional +![](//cdn.auth0.com/docs/img/linkedin-devportal-4.png) --- @@ -35,8 +33,5 @@ Create a new application and complete the form: Once the application is registered, enter your new `API Key` and `Key Secret` into the connection settings in Auth0. -![](img/linkedin-devportal-5.png) - -![](img/linkedin-devportal-6.png) diff --git a/docs/lock.md b/docs/lock.md new file mode 100644 index 00000000..883e0210 --- /dev/null +++ b/docs/lock.md @@ -0,0 +1,24 @@ +@@lock_readme@@ + +## Start using Auth0Lock + +@@lockSDK@@ + + + +[lock-repository]: https://github.com/auth0/lock +[lock-api]: https://github.com/auth0/lock#api +[lock-initialization]: https://github.com/auth0/lock/wiki/Auth0lock-initialization +[lock-customization]: https://github.com/auth0/lock/wiki/Auth0lock-customization +[ui-customization]: https://github.com/auth0/lock/wiki/UI-customization +[spa-notes]: https://github.com/auth0/lock/wiki/Types-Of-Applications#single-page-app +[webapps-notes]: https://github.com/auth0/lock/wiki/Types-Of-Applications#regular-webapp +[display-modes]: https://github.com/auth0/lock/wiki/Display-Modes +[authentication-modes]: https://github.com/auth0/lock/wiki/Authentication-Modes +[error-customization]: https://github.com/auth0/lock/wiki/Customizing-error-messages +[i18n-notes]: https://github.com/auth0/lock/wiki/I18n +[events-notes]: https://github.com/auth0/lock/wiki/Events +[development-notes]: https://github.com/auth0/lock/wiki/Development-notes +[release-process]: https://github.com/auth0/lock/wiki/Release-process +[playground-url]: http://auth0.github.io/playground/ +[migration-guide]: https://github.com/auth0/lock/wiki/Migration-guide diff --git a/docs/login-widget.md b/docs/login-widget.md index d5286fbc..e966fbe4 100644 --- a/docs/login-widget.md +++ b/docs/login-widget.md @@ -1,3 +1,5 @@ +^^warning __WARNING:__ This version of the login widget has been deprecated.
      Please use the [new version](/docs/lock) instead. + # Auth0 Login Widget The __Auth0 Login Widget__ makes it easy to integrate SSO in your app. You won't have to worry about: @@ -10,16 +12,16 @@ The __Auth0 Login Widget__ makes it easy to integrate SSO in your app. You won't ## Playground -@@sdk@@ +@@widgetSDK@@ ## Anatomy of the Auth0 Login Widget -![](img/widget-numbered.png) +![](//cdn.auth0.com/docs/img/widget-numbered.png) 1. The __title__ of the widget. You can optionally show a 24x24 icon. 2. The __social buttons__ will be shown if you have at least one social connection enabled. -3. The __Email__ field will be shown if you have at least one enterprise connection enabled. The __Password__ field will be shown if you have a Database connection. -4. The __Sign Up__ and __Forgot Password__ links will be shown if you have a Database connection. +3. The __Email__ field will be shown if you have at least one enterprise connection enabled. The __Password__ field will be shown if you have a Database connection. +4. The __Sign Up__ and __Forgot Password__ links will be shown if you have a Database connection. > **How does enterprise SSO work?** Consider a user that enters john@**fabrikam.com**. If there's an enterprise connection with an associated domain "**fabrikam.com**", then the password field will be hidden. When the user clicks on __Sign In__, he/she will be redirected to the corresponding identity provider (Google Apps, AD, Windows Azure AD, etc.) where that domain is registered. If the user is already logged in with the Identity Provider, then Single Sign On will happen. @@ -44,15 +46,15 @@ This example shows how to display the labels in Spanish: window.Auth0.signIn({ onestep: true, title: "Contoso", - signInButtonText: "Ingresar", - emailPlaceholder: "Correo", + signInButtonText: "Ingresar", + emailPlaceholder: "Correo", passwordPlaceholder: "Contraseña", separatorText: "o", showIcon: true, icon: "https://myapp.com/logo.png", showSignup: true, - signupText: "Registrarme!", - signupLink: "https://myapp.com/signup", + signupText: "Registrarme!", + signupLink: "https://myapp.com/signup", showForgot: true, forgotText: "Olvidé mi contraseña", forgotLink: "https://myapp.com/forgot" @@ -61,7 +63,7 @@ This example shows how to display the labels in Spanish: Resulting in: -![](img/widget-customized.png) +![](//cdn.auth0.com/docs/img/widget-customized.png) ## Customizing the Login Widget for Database Connections @@ -74,7 +76,7 @@ When using a database connections, the **Login Widget** has support for account separatorText: "o", showIcon: true, icon: "https://myapp.com/logo.png", - // ... other properties ... + // ... other properties ... signupTitle: "Registrarse", signupHeaderText: "Por favor, ingrese su correo y contraseña", signupFooterText: "Al hacer clic en \"Crear Usuario\", usted está aceptando nuestros términos y condiciones.", @@ -86,7 +88,7 @@ When using a database connections, the **Login Widget** has support for account Will display the following: -![](img/widget-customized-signup.png) +![](//cdn.auth0.com/docs/img/widget-customized-signup.png) ### Customizing **change-password** messages @@ -95,7 +97,7 @@ Will display the following: separatorText: "o", showIcon: true, icon: "https://myapp.com/logo.png", - // ... other properties ... + // ... other properties ... resetTitle: "Cambiar Contraseña", resetHeaderText: "Por favor, ingrese su correo y una nueva contraseña. Se le enviará un correo para confirmar el cambio de la misma.", resetButtonText: "Enviar", @@ -109,7 +111,7 @@ Will display the following: Will display the following: -![](img/widget-customized-reset.png) +![](//cdn.auth0.com/docs/img/widget-customized-reset.png) ## Customizing error messages @@ -118,7 +120,7 @@ You can also customize the error messages that will be displayed on certain situ window.Auth0.signIn({ onestep: true, title: "Contoso", - // ... other properties ... + // ... other properties ... // wrongEmailPasswordErrorText, serverErrorText, signupEnterpriseEmailWarningText, signupServerErrorText and resetServerErrorText are used only if you have a Database connection wrongEmailPasswordErrorText: 'Custom error message for invalid user/pass.', serverErrorText: 'There was an error processing the sign in.', @@ -133,7 +135,7 @@ You can also customize the error messages that will be displayed on certain situ These errors will be shown on the widget header: -![](img/widget-error.png) +![](//cdn.auth0.com/docs/img/widget-error.png) ## Sending extra query string parameters @@ -145,9 +147,10 @@ Common parameters are: * `response_type`: this could be `code` or `token`. Usually `code` is used in web apps (server-side) and `token` on mobile, native or single page apps. See [server side protocol](oauth-web-protocol) and [mobile, single page apps and native apps](oauth-implicit-protocol) sections for more information about one or the other. * `state`: arbitrary state value that will be mantained across redirects (useful for XSRF) -* `scope`: there are two possible values for scope today +* `scope`: there are various possible values for scope today: * `scope=openid`: it will return, not only the `access_token`, but also an `id_token` which is a Json Web Token (JWT). The JWT will only contain the user id. * `scope=openid%20profile`: If you want the entire user profile to be part of the `id_token`. + * `scope=openid {attr1} {attr2}`: if you want specific user profile properties returned. * `redirect_uri`: by setting this value you can choose what callback url to use if you have multiple registered. Useful when you have multiple environments (e.g. Dev & Test), and a single application in Auth0. * `authorize_url`: if specified, it will start the login transaction at that url. This is useful if you want to do something on the server, before redirecting to Auth0. For instance, this is used when integrating with ASP.NET MVC4 which uses DotNetOpenAuth and generates a proprietary "state" parameter that can only be generated on the server side. * `protocol`: this could be `oauth2` (default), `samlp` or `wsfed`. @@ -163,11 +166,11 @@ Here is an example: for (var i in client.strategies) { for (var j in client.strategies[i].connections) { var connection = client.strategies[i].connections[j]; - + var link = $('') .text(connection.name) .attr('href', connection.url); - + $('ul.login-list').append($('
    • ').append(link)); } } @@ -176,7 +179,7 @@ Here is an example: * Can I customize the colors and structure of the widget? - Yes. You can customize it by passing a `css` parameter to the script pointing to a URL with your CSS (hosted on HTTPS). Or you can use the JavaScript API and use your own UI. + Yes. You can customize it by passing a `css` parameter to the script pointing to a URL with your CSS (hosted on HTTPS). Or you can use the JavaScript API and use your own UI. Here are a couple of fiddles to play around: css customization and JavaScript API * Can I remove the "Powered by Auth0"? @@ -187,6 +190,6 @@ Here is an example: Yes. You can embed it on a `div` by adding a query string to the script URL specifying the div `id`. e.g.: <script id="auth0" src="https://sdk.auth0.com/auth0.js#client=...&**container=root**"></script> -* What happens if the user enters an email corresponding to a domain that has not been provisioned before? +* What happens if the user enters an email corresponding to a domain that has not been provisioned before? That behavior will depend on another parameter called `provisioningOnUnknownDomain`. If this flag is true, we will show the [provisioning widget](#) pre-populated with that domain. If that flag is false, an error message will be displayed (The domain has not been setup yet). diff --git a/docs/login-widget2.md b/docs/login-widget2.md new file mode 100644 index 00000000..d9f629ab --- /dev/null +++ b/docs/login-widget2.md @@ -0,0 +1,193 @@ +^^warning __WARNING:__ This version of the login widget has been deprecated.
      Please use the [new version](/docs/lock) instead. + +# Auth0 Login Widget + +The __Auth0 Login Widget__ makes it easy to integrate SSO in your app. You won't have to worry about: + +* Having a professionally looking login dialog that displays well on any resolution and device. +* Finding the right icons for popular social providers. +* Remembering what was the identity provider the user chose the last time. +* Solving the home realm discovery challenge with enterprise users (i.e.: asking the enterprise user the email, and redirecting to the right enterprise identity provider). +* Implementing a standard sign in protocol (OpenID Connect / OAuth2 Login) + +## Including the Login Widget on your page +Add the script tag to your page to get started with __Auth0 Login Widget__. + + + +## Playground + +@@widgetSDK2@@ + +## Single Page Applications + +You can handle the authorization process client-side as follows: + + + +> When `callbackOnLocationHash: true` is specified, Auth0 will send the response back as a redirect to your site passing the tokens after the hash sign: `@@account.callback@@#access_token=...&id_token=...`. + +## Customizing the Widget + +The Widget can be customized through the `options` parameter sent to the `signin` method. + +### Options + +* __connections__: Array of enabled connections that will be used for the widget. Default: all enabled connections. +* __container__: The id of the DIV where the widget will be contained. +* __icon__: Icon url. Recommended: 32x32. +* __showIcon__: Show/Hide widget icon. Default: false. +* __showForgot__: Show/Hide the "Forgot your password?" link. Default: true. +* __showSignup__: Show/Hide the "Sign Up" link. Default: true. +* __enableReturnUserExperience__: Show the account used last time the user signed in. Default: `true`. +* __userPwdConnectionName__: Specify which Database/AD-LDAP connection should be used with the Email/Password fields. Default: the first Database connection found (if it exists) or the first AD-LDAP connection found. +* __username_style__: Specify the format of the username. Options: `email` or `username`. + +> Is there an option that you think would be useful? Just open an issue on GitHub and we'll look into adding it. + +This example shows how to work with only specified connections and display the labels in Spanish: + + var widget = new Auth0Widget({ + domain: '@@account.namespace@@', + clientID: '@@account.clientId@@', + callbackURL: '@@account.callback@@', + dict: 'es' + }); + + widget.signin({ + connections: ['facebook', 'google-oauth2', 'twitter', 'Username-Password-Authentication'], + icon: 'https://contoso.com/logo-32.png', + showIcon: true + }, + function () { + // The Auth0 Widget is now loaded. + }); + +> `dict` constructor parameter is a string matching the language (`'en'`, `'es'`, `'it'`, etc.) or object containing all your customized text labels. + +Resulting in: + +![](https://i.cloudup.com/W9rpHdyIwf.png) + +## Sending extra login parameters + +You can send extra parameters when starting a login by adding them to the options object. The example below adds a `state` parameter with a value equal to `foo`. + + widget.signin({ + // ... other options ... + state: 'foo' + }); + +The following parameters are supported: `access_token`, `protocol`, `request_id`, `scope`, `state` and `connection_scopes`. + +There are other extra parameters that will depend on the provider. For example, Google allows you to get back a `refresh_token` only if you explicitly ask for `access_type=offline`. We support sending arbitrary parameters like this: + + widget.signin({ + // ... other options ... + extraParameters: { + access_type: 'offline' + } + }); + +> Note: this would be analogous to trigger the login with `https://@@account.namespace@@/authorize?state=foo&access_type=offline&...`. + +### Scope + +There are different values supported for scope: + +* `scope: 'openid'`: _(default)_ It will return, not only the `access_token`, but also an `id_token` which is a Json Web Token (JWT). The JWT will only contain the user id (sub claim). +* `scope: 'openid profile'`: If you want the entire user profile to be part of the `id_token`. +* `scope: 'openid {attr1} {attr2} {attrN}'`: If you want only specific user's attributes to be part of the `id_token` (For example: `scope: 'openid name email picture'`). + +### Connection Scopes + +The `connection_scopes` parameter allows for dynamically specifying scopes on any connection. This is useful if you want to initially start with a set of scopes (defined on the dashboard), but later on request the user for extra permissions or attributes. + +The object keys must be the names of the connections and the values must be arrays containing the scopes to request to append to the dashboard specified scopes. An example is shown below: + + widget.signin({ + connections: ['facebook', 'google-oauth2', 'twitter', 'Username-Password-Authentication', 'fabrikam.com'], + connection_scopes: { + 'facebook': ['public_profile', 'user_friends'], + 'google-oauth2': ['https://www.googleapis.com/auth/orkut'], + // none for twitter + } + } + +> The values for each scope are not transformed in any way. They must match exactly the values recognized by each identity provider. + +## Signup and Reset + +It is also possible to start the widget in the **Sign Up** mode or **Reset Password** mode as follows: + + widget.signup(/* [same as the .signin method] */); + // or + widget.reset(/* [same as the .signin method] */); + +## Anatomy of the Auth0 Login Widget + +![](https://i.cloudup.com/y2iG3mE8Ll.png) + +1. The __title__ of the widget. You can optionally show a 32x32 icon. +2. The __social buttons__ will be shown if you have at least one social connection enabled. +3. The __Email__ field will be shown if you have at least one enterprise connection enabled. The __Password__ field will be shown if you have a Database connection. +4. The __Sign Up__ and __Forgot Password__ links will be shown if you have a Database connection. + +> **How does enterprise SSO work?** Consider a user that enters john@**fabrikam.com**. If there's an enterprise connection whose primary or additional domains match "**fabrikam.com**", then the password field will be hidden. When the user clicks on __Sign In__, he/she will be redirected to the corresponding identity provider (Google Apps, AD, Windows Azure AD, etc.) where that domain is registered. If the user is already logged in with the Identity Provider, then Single Sign On will happen. + +## Customize the look and feel + +You can apply your own style to the elements. All classes and ids are prefixed with `a0-` to avoid conflicts with your own stylesheets. + +## Customizing error messages + +You can also customize the error messages that will be displayed on certain situations: + + var widget = new Auth0Widget({ + // ... other parameters ... + dict: { + loadingTitle: 'loading...', + close: 'close', + signin: { + wrongEmailPasswordErrorText: 'Custom error message for invalid user/pass.', + serverErrorText: 'There was an error processing the sign in.', + strategyEmailInvalid: 'The email is invalid.', + strategyDomainInvalid: 'The domain {domain} has not been setup.' + }, + signup: { + serverErrorText: 'There was an error processing the sign up.', + enterpriseEmailWarningText: 'This domain {domain} has been configured for Single Sign On and you can\'t create an account. Try signing in instead.' + }, + reset: { + serverErrorText: 'There was an error processing the reset password.' + } + // wrongEmailPasswordErrorText, serverErrorText, enterpriseEmailWarningText are used only if you have a Database connection + // strategyEmailInvalid is shown if the email is not valid + // strategyDomainInvalid is shown if the email does not have a matching enterprise connection + } + }); + +These errors will be shown on the widget header: + +![](https://i.cloudup.com/9AfFb-pbwm.png) diff --git a/docs/logout.md b/docs/logout.md index 2821c642..0b3fe463 100644 --- a/docs/logout.md +++ b/docs/logout.md @@ -4,10 +4,19 @@ You can logout the user from the identity provider by doing a redirect to: https://@@account.namespace@@/logout -Depending on which identity provider the user has logged in to, it will take care of logging the user out of that identity provider +Depending on which identity provider the user has logged in to, it will take care of logging the user out of that identity provider. +> If you are working with social identity providers (like Google, Facebook, etc.) make sure to set your own Client ID and Secret on the Auth0 dashboard, otherwise the logout won't work. Also, beware that when you use this, the user will be logged out from the social provider completely. For instance, if the user had Gmail open on another tab, it will close that session as well. This might not be desirable for the user though, so make sure you tailor the experience according to your usecase. -> For example, if the user has logged in with Google, when you redirect to this endpoint it will logout the user from Google. This might not be desirable for the user though, so make sure you tailor the experience according to your usecase. +If you specify a `returnTo` parameter, we will redirect to the url specified after the logout. https://@@account.namespace@@/logout?returnTo=http://somewhere -If you specifcy a `returnTo` parameter, we will redirect to the url specified after the logout. \ No newline at end of file +> The returnTo parameter won't work for social providers, since there is no way to specify that. + +Facebook has some special requirements to trigger a logout. You will have to build the logout URL as described below: + + https://@@account.namespace@@/logout? + returnTo=url_encode(https://@@account.namespace@@/logout?returnTo=http://yoursite) + &access_token=[facebook access_token] + +> Make sure to properly encode the returnTo parameter diff --git a/docs/mfa.md b/docs/mfa.md new file mode 100644 index 00000000..86482328 --- /dev/null +++ b/docs/mfa.md @@ -0,0 +1,82 @@ +# Multi-factor Authentication in Auth0 + +> This feature is in __beta__. Please [contact us](mailto://support@auth0.com) if you have any questions or if you'd like integration with a specific system not listed here. + +> **Warning**: Multifactor authentication does not work with the `/ro` (Resource Owner) endpoint. If using MFA for database connections that use popup mode, `sso: true` needs to be added to the options for auth0.js or Lock. Failing to do so will result in users being able to log in without MFA checks. [More information on `sso` parameter](https://github.com/auth0/auth0.js#popup-mode). + +Auth0 ships with built-in support for popular, multi-factor authentication systems. We currently have these two systems enabled: + +* [Google Authenticator](http://en.wikipedia.org/wiki/Google_Authenticator) +* [Duo Security](https://www.duosecurity.com/) + +Multi-factor authentication is enabled through [Rules](rules). This gives you full control of what conditions trigger the process. Common conditions are: the destination application, the type of authentication used, the time of the day, the location, etc. + +Once the user logged in using a second factor you will get back an extra property specifying that a second factor was used and which one. + +``` +"multifactor": [ + "google-authenticator" +] +``` + + +## Google Authenticator + +``` +function (user, context, callback) { + + if( conditionIsMet() ){ + context.multifactor = { + provider: 'google-authenticator' + }; + } + + callback(null, user, context); +} +``` + +## Duo Security + +``` +function (user, context, callback) { + + if( conditionIsMet() ){ + context.multifactor = { + provider: 'duo', + ikey: '{your Duo integration key}', + skey: '{your Duo secret key}', + host: '{your endpoint}.duosecurity.com' + }; + } + + callback(null, user, context); +} +``` +## Session + +By default multifactor is requested only once per month. You can change this by disabling the cookie with `ignoreCookie` as follows: + +``` +function (user, context, callback) { + + if( conditionIsMet() ){ + context.multifactor = { + ignoreCookie: true, + provider: 'google-authenticator' + }; + } + + callback(null, user, context); +} +``` + +## Simple demo + +This demo illustrates an app in which __Duo Security__ has been enabled. + +1. User logs in with Windows Live. +2. User is challenged with an additional authentication factor (in this example the enrollment process has been completed). +3. User accesses the app. + + +![](//cdn.auth0.com/docs/img/duo.gif) diff --git a/docs/migrating.md b/docs/migrating.md new file mode 100644 index 00000000..4eeb9cc1 --- /dev/null +++ b/docs/migrating.md @@ -0,0 +1,49 @@ +# Importing users to Auth0 + +Our focus has always been not only greenfield projects but also existing applications that want to extend their Authentication capabilities. + +We have released a new feature that enables the gradual migration users from an existing **Database Connection** to Auth0. + +## How to enable it? + +Check the "Import Users to Auth0" option in the connection settings: + +![](//cdn.auth0.com/docs/img/migrating-1.png) + +## How does it work? + +When you write a **login script** in the database connection to authenticate users, for instance: + +```javascript +function (email, password, callback) { + + // validate user/password against your existing database + request.get({ + url: 'https://myapp.com/existingdatabase/login', + auth: { + username: email, + password: password + } + }, function (err, response, body) { + if (err) return callback(err); + if (response.statusCode === 401) return callback(); + var user = JSON.parse(body); + + // return the user information that you + // want to persist into Auth0 as a new user + callback(null, { + user_id: user.user_id.toString(), + nickname: user.nickname, + email: user.email + }); + }); +} +``` + +Then when a user authenticates, the following process take place: + +![](//cdn.auth0.com/docs/img/migrating-2.png) + +So for example, let's say you have a MySQL database and you are hashing passwords with SHA1. You would define the script that connects to MySQL, gets the user from the DB and then uses `crypto.createHash('sha1')` to hash the password and check against the stored password. If that was succesful, the user will be automatically created in Auth0 database and his/her password will be hashed using `bcrypt` with 12 iterations (our default hashing algorithm). Next time a user logs in, we will check against OUR database and our hash. + +> Note: Password resets will only affect the users stored in Auth0, and new users will be stored in Auth0 only. diff --git a/docs/miicard-clientid.md b/docs/miicard-clientid.md new file mode 100644 index 00000000..b1e34a90 --- /dev/null +++ b/docs/miicard-clientid.md @@ -0,0 +1,8 @@ +# Obtaining a Consumer Key and Consumer Secret for miiCard + +Please contact miiCard to get [self-management](http://www.miicard.com/developers/self-management) capabilities on your miiCard account. + +Once this is enabled, you will be able to complete the Auth0 setup. The `Consumer Key` and `Consumer Secret` will be available on the __miiCard Business Portal__: + +![](//cdn.auth0.com/docs/img/miicard-businessportal.png) + diff --git a/docs/monitoring.md b/docs/monitoring.md new file mode 100644 index 00000000..45684a98 --- /dev/null +++ b/docs/monitoring.md @@ -0,0 +1,137 @@ +# Monitoring Auth0 + +If you are using the public cloud version of Auth0 we recommend you subscribe to [http://status.auth0.com](http://status.auth0.com) for a continuous stream of notifications regarding the availability of the service. Any incidents are reported there by the Auth0 Devops team. Current and historical uptime is available on [http://uptime.auth0.com](http://uptime.auth0.com). + +## Monitoring your own Auth0 account + +You can add Auth0 health probes to your own monitoring infrastructure easily by querying these two endpoints: + + https://@@account.namespace@@/test + +This should return a Json object with a single property: + +``` +200 +content-type: application/json +{"clock":1417220191640} +``` + +This other one: + + https://@@account.namespace@@/testall + +returns a simple text: + +``` +200 +content-type: text/plain +OK +``` + +Each of these tests verifies correct functioning of various components of the server, memory consumption, I/O operations, database, etc. + +If you extended Auth0 through [rules](rules) or [a custom db connection](mysql-connection-tutorial), you can also build a synthetic transaction that excercises these capabilities. We recommend using an authentication flow that won't require a UI (e.g. `Resource Owner flow`). Other ones might require a monitoring tool able to mimick what a user would do (e.g. follow redirects, input username/password on a form, etc.). + +``` +POST https://@@account.namespace@@/oauth/ro +Content-Type: 'application/json' +{ + "client_id": "{An app registered in Auth0 for monitoring}", + "username": "{A system account for monitoring}", + "password": "{A password}", + "connection": "{A user store defined in Auth0}", + "grant_type": "password", + "scope": "openid", + "device": "SCOM" +} +``` + +A successful request would return: + +``` +HTTP 200 +{ + "id_token": "eyJ0eXAi......3Jia5WgM", + "access_token": "F25VQ.....NWpS", + "token_type": "bearer" +} +``` + +Many tools exist for monitoring using this approach: [New Relic](http://newrelic.com), [Pingdom](http://pingdom.com), etc. + +--- + +## Monitoring a private deployment + +If you are using the __Auth0 Appliance__, monitoring is very similar to the steps described above. + +The health endpoints are equivalent, only with the private URL: + + https://{your_auth0_server}/{test | testall} + +In a dedicated deployment we recommend you monitor the following endpoints: + +* __Dashboard__: `https://app.myauth0.com/test` +* __Documentation site__: `https://docs.myauth0.com/test` +* __Login endpoints__: `https://login.myauth0.com/test` and `https://login.myauth0.com/lo/test` + +As before, the above endpoints return a timestamp: + +``` +200 +content-type: application/json +{"clock":1417196777540} +``` + +### Monitoring individual nodes of a cluster + +The endpoints above will normally hit the load-balancer that is fronting the nodes of a cluster. We also recommend you monitor individual nodes. A typical highly-available deployment will have at leasts 3 nodes: + +* `https://{IP Address Node 1}/testall` +* `https://{IP Address Node 2}/testall` +* `https://{IP Address Node 3}/testall` + +If all is working fine, the endpoints will return a simple string: + +``` +200 +content-type: text/plain +OK +``` + +Individual nodes that are not responding, or timeout can be __removed from the load balancer without affecting the service__. All nodes of a cluster can serve requests to client applications. All configuration information is continruously replicated across nodes. + +> If a node stops responding, contact [Auth0 Support](mailto://support@auth0.com). + +### Configuring SCOM + +Auth0 can be monitored as a standard web application on System Center Operations Manager (or any other similar tool that supports synthetic transactions). + +We recommend adding probes in SCOM for all the endpoints describe before, including a login synthetic transaction. + +#### Configuring System Center Operations Manager + +Setup for SCOM is straight forward as shown on these screenshots: + +![ss-2014-11-21T15-44-34.png](https://s3.amazonaws.com/blog.auth0.com/ss-2014-11-21T15-44-34.png) + +![ss-2014-11-21T16-31-15.png](https://s3.amazonaws.com/blog.auth0.com/ss-2014-11-21T16-31-15.png) + +![ss-2014-11-21T16-32-25.png](https://s3.amazonaws.com/blog.auth0.com/ss-2014-11-21T16-32-25.png) + +![ss-2014-11-21T16-33-51.png](https://s3.amazonaws.com/blog.auth0.com/ss-2014-11-21T16-33-51.png) + +![ss-2014-11-21T16-34-25.png](https://s3.amazonaws.com/blog.auth0.com/ss-2014-11-21T16-34-25.png) + +Make sure to configure proper alerts against these probes. Timeouts on endpoints are dependent on the network configuration, but should resemble the expected behavior of applications. + +#### Monitoring + +You can monitor System Center activity throught the monitoring tab as shown bellow: + +![ss-2014-11-25T17-20-47.png](https://s3.amazonaws.com/blog.auth0.com/ss-2014-11-25T17-20-47.png) + +![ss-2014-11-25T17-22-10.png](https://s3.amazonaws.com/blog.auth0.com/ss-2014-11-25T17-22-10.png) + +> If any of these alarms are triggered, contact [Auth0 support](mailto://support@auth0.com) immediately. + diff --git a/docs/ms-account-clientid.md b/docs/ms-account-clientid.md index 84d1af5a..34492338 100644 --- a/docs/ms-account-clientid.md +++ b/docs/ms-account-clientid.md @@ -1,20 +1,20 @@ # Obtaining a ClientId and Client Secret in Microsoft Account -To configure Microsoft Account OAuth connections you need to register an application in the Live Connect Developer Center. +To configure Microsoft Account OAuth connections you need to register an application in the Live Connect Developer Center. ##1. Log in into Live Connect Developer Center -Log into [the Developer Center](http://msdn.microsoft.com/en-us/live/ff519582), then select __My Apps__: +Log into [the Developer Center](https://account.live.com/developers/applications), then select __My Apps__: -![](img/ma-portal-1.png) +![](//cdn.auth0.com/docs/img/ma-portal-1.png) --- ##2. Create an application -![](img/ma-portal-2.png) +![](//cdn.auth0.com/docs/img/ma-portal-2.png) -After the app is created, copy the `ClientId` and `ClientSecret` and enter them into your connection settings. +After the app is created, copy the `ClientId` and `ClientSecret` and enter them into your connection settings. Also, make sure you setup the callback URL for the app. That information should go into the `redirect domain` textbox and for your setup is: - http://@@account.namespace@@/login/callback + https://@@account.namespace@@/login/callback diff --git a/docs/mvc-tutorial-enterprise.md b/docs/mvc-tutorial-enterprise.md index c0d64bd5..42249d89 100644 --- a/docs/mvc-tutorial-enterprise.md +++ b/docs/mvc-tutorial-enterprise.md @@ -1,6 +1,6 @@ # Using Auth0 with ASP.NET MVC 4 - Enterprise Providers -The true power of Auth0 is that it open new possibilities to integrate your application with your customers user directories. In this tutorial we will guide you through a series of steps to enable enterprise single-sign on in your Asp.Net MVC 4 application. +The true power of Auth0 is that it open new possibilities to integrate your application with your customers user directories. In this tutorial we will guide you through a series of steps to enable enterprise single-sign on in your Asp.Net MVC 4 application. ##Before you start @@ -9,9 +9,9 @@ The true power of Auth0 is that it open new possibilities to integrate your appl ##Integrating Auth0 with MVC4 -###1. Enable an enterprise provider +###1. Enable an enterprise provider -Go to [Enterprise Connections](@@uiURL@@/#/connections/enterprise) in your dashboard and enable Google-Apps connections by providing the same credentials you use for Google OAuth2 in the previous tutorial. +Go to [Enterprise Connections](@@uiURL@@/#/connections/enterprise) in your dashboard and enable Google-Apps connections by providing the same credentials you use for Google OAuth2 in the previous tutorial. ###2. Create a new provisioning route @@ -71,18 +71,18 @@ After an administrator of the domain follows the provisioning link, he will be r { if (granted && !string.IsNullOrEmpty(domain)) { - OAuthWebSecurity.RegisterClient(new Auth0.OpenAuthClient(connection, + OAuthWebSecurity.RegisterClient(new Auth0.OpenAuthClient(connection, "@@account.clientId@@", "@@account.clientSecret@@", - "@@account.namespace@@", + "@@account.namespace@@", connection), connection, new Dictionary()); - + ViewBag.domain = domain; ViewBag.connection = connection; return View("Domain_Granted"); } - //.... same code you have before next + //.... same code you have before next ####4.b Create a view named ```Domain_Granted``` in ```Views\Account```: @@ -112,7 +112,7 @@ Create a new controller named EnterpriseLoginController: public ActionResult Open(string domain) { return new AccountController.ExternalLoginResult( - domain, + domain, Url.Action("ExternalLoginCallback", "Account")); } } @@ -128,16 +128,16 @@ Modify the ```RouteConfig.cs``` file and add a new route **before** the default And modify the ```Domain_Granted```, add the following link: - login as @ViewBag.domain + login as @ViewBag.domain When Enterprise Users visit this URL they are automatically redirected to their company login page: -![](img/enterprise-login.png) +![](//cdn.auth0.com/docs/img/enterprise-login.png) ## Testing the app: Open a browser, navigate to the website and press the **sign up my company** link. You should see the provisioning widget and if you have a google account you will be able to register a company: -![](img/widget-prov-in-aspnet.png) +![](//cdn.auth0.com/docs/img/widget-prov-in-aspnet.png) -Congratulations! \ No newline at end of file +Congratulations! \ No newline at end of file diff --git a/docs/mvc-tutorial.md b/docs/mvc-tutorial.md deleted file mode 100644 index 83fea88c..00000000 --- a/docs/mvc-tutorial.md +++ /dev/null @@ -1,100 +0,0 @@ -# Using Auth0 with ASP.NET MVC 4 - -This tutorial is specific to the new ASP.NET MVC4 identity mechanism based on [OAuth providers feature](http://www.asp.net/mvc/overview/getting-started/using-oauth-providers-with-mvc). - -> **IMPORTANT**: Make sure to understand [how this new mechanism work](http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx) before moving forward. If this approach does not fit to you, follow the [generic ASP.NET tutorial](aspnet-tutorial). - -## Tutorial - -###1. Create a simple MVC website and install Auth0 client - -For this example, we will use the standard template that ships with Visual Studio 2012. Select __"FILE -> New project -> MVC 4 Web Application -> Internet Application"__ - -Once the default template unfolds, use NuGet to install the **DotNetOpenAuth-Auth0**, running the command: - - Install-Package DotNetOpenAuth-Auth0 - -![](img/install-dotnetopenauth-auth0-nuget.png) - -> [DotNetOpenAuth-Auth0](https://nuget.org/packages/DotNetOpenAuth-Auth0) is a helper class that plugs into DotNetOpenAuth. Most of the heavylifting is actually done by the __OAuth2Client__ though. Our helper just makes sure you are using the right endpoints and sending the right parameters to Auth0. -> - -###2. Register Auth0Client in the AuthConfig - -Open the AuthConfig.cs file (under `App_Start` folder), and look for the ```RegisterAuth``` method: - - public static class AuthConfig - { - public static void RegisterAuth() - { - - Auth0.OpenAuthClient.RegisterAllSocialProviders( - "@@account.clientId@@", - "@@account.clientSecret@@", - "@@account.namespace@@"); - - } - } - -> This will query the Auth0 HTTP Api and list all the social providers you have enabled. Then it will register in DotNetOpenAuth a client per each provider. - - -###3. Setup the callback URL in Auth0 - -Go to [Settings](@@uiURL@@/#/settings) and make sure to set the callback URL to this: - -``` -http://localhost:port/Account/ExternalLoginCallback -``` - -![](img/settings-callback.png) - -## Testing the app: - -Open a browser, navigate to the website and press the login button. You should see "Google" in the right panel. - -Congratulations! - -###4. Client-side integration and auth0 widget - -__This step is optional.__ We have a beautiful widget that you can integrate in your application and replace the default from Asp.Net MVC. - -Create a new route in the ```AccountController.cs``` file: - - // - // POST: /Account/Auth0Login - - [AllowAnonymous] - public ActionResult Auth0Login(string connection) - { - return new ExternalLoginResult(connection, Url.Action("ExternalLoginCallback")); - } - -Open the ```views\shared\_LoginPartial.cshtml``` and change its content to something as follows: - - @if (Request.IsAuthenticated) { - - Hello, @Html.ActionLink(User.Identity.Name, "Manage", "Account", routeValues: null, htmlAttributes: new { @class = "username", title = "Manage" })! - @using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm" })) { - @Html.AntiForgeryToken() - Log off - } - - } else { - - - } - -> Notice that we have changed the ```else``` code block to use our auth0 widget. - -Next time, when you press the login button, you should see something like this: - -![](img/widget-in-aspnet.png) - -> **IMPORTANT**: please make sure you have the ```&authorize_url=/Account/Auth0Login``` part in the script tag. - -The widget can be customized, read more about how to do it [here](login-widget). - -> The great power of Auth0 is in Enterprise connections, please follow the [next tutorial](/mvc-tutorial-enterprise). \ No newline at end of file diff --git a/docs/mysql-connection-tutorial.md b/docs/mysql-connection-tutorial.md index 64b3ca22..d65a42c1 100644 --- a/docs/mysql-connection-tutorial.md +++ b/docs/mysql-connection-tutorial.md @@ -10,60 +10,59 @@ In this tutorial we will guide you through a series of steps to plug your users Log in into Auth0, select the __Connections__ menu option, and then __Database__. Create a new name for the database connection. You can choose any name. It is not important at this time. -![](/img/db-connection-create.png) +![](//cdn.auth0.com/docs/img/db-connection-create.png) ## Customize the database connection -Auth0 ships with multiple templates to connect with common and widely used database systems: __MySQL__, __SQL Server__, __SQL Azure__, __MongoDb__, among others. +Auth0 ships with multiple templates to connect with common and widely used database systems: __MySQL__, __SQL Server__, __SQL Azure__, __MongoDb__, __Couchbase__, __Postgres__, among others. In this tutorial, we will use __MySQL__. Select __MySQL__ from one of the templates available: -![](/img/db-connection-login-script.png) +![](//cdn.auth0.com/docs/img/db-connection-login-script.png) -The following code will be generated from for you in the editor below: +The following code will be generated for you in the editor below: - function login (username, password, callback) { - var connection = mysql({ - host : 'localhost', - user : 'me', - password : 'secret', - database : 'mydb' - }); + function login (email, password, callback) { + var connection = mysql({ + host : 'localhost', + user : 'me', + password : 'secret', + database : 'mydb' + }); - connection.connect(); - - var query = "SELECT Id, Nickname, Email, FirstName, LastName, Password " + - "FROM MyUsers WHERE Nickname = ?"; - - connection.query(query, [username], function (err, results) { - if (err) return callback(err); - if (results.length === 0) return callback(); - var user = results[0]; - - if (!bcrypt.compareSync(password, user.Password)) return callback(); - - callback(null, { - id: user.Id.toString(), - username: user.Email, - displayName: (user.FirstName || '') + ' ' + (user.LastName || ''), - name: { - givenName: user.FirstName, - familyName: user.LastName - }, - emails: [ {value: user.Email} ] - }); - }); - } + connection.connect(); -As you can see, this script connects to a __MySQL__ database and executes a query to retrieve the first user with a `Nickname == username`. It then validates that the passwords match (with the `bcrypt.compareSync` method), and if successful, it will finally return an object with some user profile information: `id`, `username`, `displayName`, `emails`. + var query = "SELECT id, nickname, email, password " + + "FROM users WHERE email = ?"; -This script assumes you have a `MyUsers` table with all these columns. You can of course tweak this script in order to adjust it to your own requirements. + connection.query(query, [email], function (err, results) { + if (err) return callback(err); + if (results.length === 0) return callback(); + var user = results[0]; + + if (!bcrypt.compareSync(password, user.password)) { + return callback(); + } + + callback(null, { + id: user.id.toString(), + nickname: user.nickname, + email: user.email + }); + + }); + + } + +As you can see, this script connects to a __MySQL__ database and executes a query to retrieve the first user with `email == user.email`. It then validates that the passwords match (with the `bcrypt.compareSync` method), and if successful, it will finally return an object with some user profile information: `id`, `nickname`, `email`. + +This script assumes you have a `users` table with all these columns. You can of course tweak this script in order to adjust it to your own requirements. ## Configuration At the bottom of the editor you will find a way of storing parameters securely. This is convenient for storing the credentials used to connect to the database: -![](/img/db-connection-configurate.png) +![](//cdn.auth0.com/docs/img/db-connection-configurate.png) In the script you would refer to these parameters as: ```configuration.PARAMETER_NAME```. For example, you could write: @@ -77,34 +76,70 @@ In the script you would refer to these parameters as: ```configuration.PARAMETER ## Debugging and troubleshooting -You can test the script using the ```try``` button. If the result is okay you will see a green border and the resulting profile: +You can test the script using the ```try``` button. If the result is okay you will see the resulting profile: -![](/img/db-connection-try-ok.png) +![](//cdn.auth0.com/docs/img/db-connection-try-ok.png) If the script failed for some reason, you will see a red border and the errors that occurred: -![](/img/db-connection-try-fail.png) +![](//cdn.auth0.com/docs/img/db-connection-try-fail.png) JavaScript's `console.log` is supported, so you can output more details: -![](/img/db-connection-console-log.png) +![](//cdn.auth0.com/docs/img/db-connection-console-log.png) ## Auth0 Login Widget After you have enabled the database connection, Auth0's widget will automatically change the appearance to allow users to enter `username` and `password`. These will be inputs to your script. -![](/img/db-connection-widget.png) +![](//cdn.auth0.com/docs/img/db-connection-widget.png) ## How it works The script runs in a JavaScript sandbox where you can use the full power of the language and selected libraries. The current API supports: -- [mysql](https://github.com/felixge/node-mysql) -- [sqlserver](https://github.com/pekim/tedious) -- [request](https://github.com/mikeal/request) -- [crypto](http://nodejs.org/api/crypto.html) -- [Buffer](http://nodejs.org/api/buffer.html) -- [bcrypt](https://github.com/ncb000gt/node.bcrypt.js/) -- [pbkdf2](https://github.com/davidmurdoch/easy-pbkdf2) - -> Do you need support for other libraries? Contact us: [support@auth0.com](mailto:support@auth0.com?subject=Libraries in custom connection) \ No newline at end of file +###Utilities +* [bcrypt](https://github.com/ncb000gt/node.bcrypt.js/) _(~0.7.5)_ +* [Buffer](http://nodejs.org/docs/v0.8.26/api/buffer.html) +* [crypto](http://nodejs.org/docs/v0.8.26/api/crypto.html) +* [pbkdf2](https://github.com/davidmurdoch/easy-pbkdf2) _(0.0.2)_ +* [q](https://github.com/kriskowal/q) _(~1.0.1)_ +* [request](https://github.com/mikeal/request) _(~2.21.0)_ +* [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) _(~0.2.8)_ +* [xmldom](https://github.com/jindw/xmldom) _(~0.1.16)_ +* [xpath](https://github.com/goto100/xpath) _(0.0.5)_ +* [xtend](https://github.com/Raynos/xtend) _(~1.0.3)_ + +###Databases +* [cassandra](https://www.npmjs.org/package/node-cassandra-cql) _(^0.4.4)_ +* [couchbase](https://github.com/couchbase/couchnode) _(~1.2.1)_ +* [mongo](https://github.com/mongodb/node-mongodb-native) _(~1.3.15)_ + * [BSON](http://mongodb.github.io/node-mongodb-native/api-bson-generated/bson.html) + * [Double](http://mongodb.github.io/node-mongodb-native/api-bson-generated/double.html) + * [Long](http://mongodb.github.io/node-mongodb-native/api-bson-generated/long.html) + * [ObjectID](http://mongodb.github.io/node-mongodb-native/api-bson-generated/objectid.html) + * [Timestamp](http://mongodb.github.io/node-mongodb-native/api-bson-generated/timestamp.html) +* [knex](http://knexjs.org) _(~0.6.3)_ + * The function returned by `require('knex')` is available as `Knex`. +* [mysql](https://github.com/felixge/node-mysql) _(~2.3.2)_ + * [mysql\_pool](https://github.com/felixge/node-mysql#pool-options) +* [postgres](http://github.com/brianc/node-postgres) _(~2.8.3)_ +* [sqlserver](https://github.com/pekim/tedious) _(~0.1.4)_ + +###Errors + +To return an error simple call the callback with an error as the first parameter: + + callback(error); + +There are three different types of errors you can return from a DB Connection: + +- `new WrongUsernameOrPasswordError(, )`: Use this error when you know who is the user and you want to keep track of the wrong password. +- `new ValidationError(, )`: Generic error with an error code. +- `new Error()`: Simple errors (no error code). + +Example: + + callback(new ValidationError('email-too-long', 'Email is too long.')); + +> Do you need support for other libraries? Contact us: [support@auth0.com](mailto:support@auth0.com?subject=Libraries in custom connection) diff --git a/docs/native-platforms/android.md b/docs/native-platforms/android.md new file mode 100644 index 00000000..6e26fcdd --- /dev/null +++ b/docs/native-platforms/android.md @@ -0,0 +1,203 @@ +--- +lodash: true +--- + +## Android Tutorial + +<% if (configuration.api && configuration.thirdParty) { %> + + + +<% } else { %> + + + +<% } %> + +**Otherwise, if you already have an existing application, please follow the steps below.** + +### Before Starting + +
      +

      Go to the Application Settings section in the Auth0 dashboard and make sure that Allowed Callback URLs contains the following value:

      + +
      a0@@account.clientId@@://*.auth0.com/authorize
      +
      + +### 1. Adding Auth0 Lock to your project + +Add the following to the `buid.gradle`: + +```gradle +compile 'com.auth0.android:lock:1.1.+' +compile 'com.auth0.android:lock-facebook:1.1.+' +compile 'com.auth0.android:lock-googleplus:1.1.+' +``` + +### 2. Configuring Auth0 Credentials & Callbacks + +Add the `android.permission.INTERNET` permission: + +```xml + +``` + +Then add the following entries to `AndroidManifest.xml` inside the `` tag: + +```xml + + + + + + + + + + + + +``` + + +### 3. Initialise Lock + +First make your Application class (The one that extends from `android.app.Application`) implement the interface `com.auth0.lock.LockProvider` and add these lines of code: + +```java +public class MyApplication extends Application implements LockProvider { + + private Lock lock; + + public void onCreate() { + super.onCreate(); + lock = new LockBuilder() + .loadFromApplication(this) + /** Other configuration goes here */ + .closable(true) + .build(); + } + + @Override + public Lock getLock() { + return lock; + } +} +``` + +### 3. Register Native Authentication Handlers + +You can configure Lock to use other native android apps to log the user in (e.g: Facebook, Google+, etc.). In order to do so, you'll need to register them with Lock after it's initialised. + +> If you don't want to use Facebook nor Google+ native authentication, please go directly to the [next step](#9) + +#### Facebook + +Lock uses the native Facebook SDK to obtain the user's access token. This means that you'll need to configure it for your app. + +To get started, in your AndroidManifest.xml you need to add the following: + +```xml + + +``` + +Where `@string/facebook_app_id` is your Facebook Application ID that you can get from [Facebook Dev Site](https://developers.facebook.com/apps). + +> For more information on how to configure this, please check [Facebook Getting Started Guide](https://developers.facebook.com/docs/android/getting-started). + +> **Note:** The Facebook app should be the same as the one set in Facebook's Connection settings on your Auth0 account + +Finally, you need to register the Auth0 Facebook Identity Provider with Lock. This must be done after Lock is initialised in your `Application` class: + +```java +FacebookIdentityProvider facebook = new FacebookIdentityProvider(lock); +lock.setProvider(Strategies.Facebook.getName(), facebook); +``` + +####Google+ + +To support Google+ native authentication you need to add the following permissions and meta-data to your `AndroidManifest.xml`: + +```xml + + + +``` + +Finally, you need to register the Auth0 Google+ Identity Provider with Lock. This must be done after Lock is initialised in your `Application` class: + +```java +GooglePlusIdentityProvider googleplus = new GooglePlusIdentityProvider(lock, this); +lock.setProvider(Strategies.GooglePlus.getName(), googleplus); +``` + +### 4. Let's implement the login + +Now we're ready to implement the Login. + +We can show the Login Dialog by starting the activity `LockActivity`. + +```java +Intent lockIntent = new Intent(this, LockActivity.class); +startActivity(lockIntent); +``` + +[![Lock.png](http://blog.auth0.com.s3.amazonaws.com/Lock-Widget-Android-Screenshot.png)](https://auth0.com) + +> **Note**: There are multiple ways of implementing the login box. What you see above is the Login Widget, but if you want, you can use your own UI. +> Or you can also try our passwordless Login Widget [SMS](https://github.com/auth0/Lock.Android#sms) + +Once the user logs in, we have to register to the `Lock.AUTHENTICATION_ACTION` from a `LocalBroadcastManager` to receive the user profile and tokens. + +```java +broadcastManager = LocalBroadcastManager.getInstance(this); +broadcastManager.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + UserProfile profile = intent.getParcelableExtra(Lock.AUTHENTICATION_ACTION_PROFILE_PARAMETER); + Token token = intent.getParcelableExtra(Lock.AUTHENTICATION_ACTION_TOKEN_PARAMETER); + //Your code to handle login information. + } + }, new IntentFilter(Lock.AUTHENTICATION_ACTION)); +``` + +### 5. Showing user information + +After the user has logged in, we can use the `UserProfile` object to display the user's information. + +```java + TextView usernameTextView = //find your TextView + TextView emailTextView = //find your TextView + usernameTextView.setText(profile.getName()); + emailTextView.setText(profile.getEmail()); +``` + +> You can [click here](@@base_url@@/user-profile) to find out all of the available properties from the user's profile or you can check [UserProfile](https://github.com/auth0/Lock.Android/blob/master/android-core/src/main/java/com/auth0/core/UserProfile.java). Please note that some of this depend on the social provider being used. + +### 6. We're done + +You've implemented Login and Signup with Auth0 in Android. You're awesome! + diff --git a/docs/native-platforms/cordova.md b/docs/native-platforms/cordova.md new file mode 100644 index 00000000..061ab384 --- /dev/null +++ b/docs/native-platforms/cordova.md @@ -0,0 +1,106 @@ +--- +lodash: true +--- + +## Cordova Tutorial + +<% if (configuration.api && configuration.thirdParty) { %> + + + +<% } else { %> + + + +<% } %> + +**Otherwise, if you already have an existing application, please follow the steps below.** + +### 0. Setting up the callback URL in Auth0 + +
      +

      Go to the Application Settings section in the Auth0 dashboard and make sure that Allowed Callback URLs contains the following value:

      + +
      https://@@account.namespace@@/mobile
      + +

      Also, if you are testing your application locally, make sure to add your local URL as an Allowed Callback URL and the following as an Allowed Origin (CORS):

      + +
      file://*
      + +
      + +### 1. Add `InAppBrowser` plugin + +You must install the `InAppBrowser` plugin from Cordova to be able to show the Login popup. For that, just run the following command: + +````bash +cordova plugin add org.apache.cordova.inappbrowser +``` + +and then add the following configuration to the `config.xml` file: + +````xml + + + + +``` + +### 2. Follow the guide specific to the FrontEnd technology you're using + +Now, you can just follow the tutorial for the FrontEnd technology that you're using. We currently support applications using [jQuery](@@base_url@@/new/client-platforms/jquery), [AngularJS](@@base_url@@/new/client-platforms/angularjs) and [Vanilla JS](@@base_url@@/new/client-platforms/vanillajs). + +> **Warning**: Cordova doesn't support getting dependencies from a CDN, so you're going to have to download the JS and CSS dependencies locally and then point to the downloaded files. + +> **Warning**: You must use `popup` mode when configuring an application with Cordova. (All available guides currently do that by default) + +### 3. Sit back and relax + +Now it's time to sit back, relax and open a beer. You've implemented Login and Signup with Auth0 and Cordova. + +### Troubleshooting + +#### Command failed with exit code 65 when running ionic build + +This means that the `InAppBrowser` plugin wasn't installed successfully by Cordova. Try any of the followings to fix this. + +* Reinstall the `InAppBrowser` plugin + +````bash +ionic plugin remove org.apache.cordova.inappbrowser +ionic plugin add org.apache.cordova.inappbrowser +``` +* Remove the platform and re add it + +````bash +ionic platform remove ios +ionic platform add ios +``` + +* Copy the contents from the plugin to the platform plugins + +````bash +cp plugins/org.apache.cordova.inappbrowser/src/ios/* platforms/ios/[yourAppName]/Plugins/org.apache.cordova.inappbrowser/ +``` + +#### Get a blank page with an OK after signin + +This means that the `InAppBrowser` plugin wasn't installed successfully by Cordova. See the previous section to learn how to solve this. diff --git a/docs/native-platforms/ionic.md b/docs/native-platforms/ionic.md new file mode 100644 index 00000000..e6994f0e --- /dev/null +++ b/docs/native-platforms/ionic.md @@ -0,0 +1,287 @@ +--- +lodash: true +--- + +## Ionic Framework Tutorial + + + +**Otherwise, if you already have an existing application, please follow the steps below.** + +### 1. Setting up the callback URL in Auth0 + +
      +

      Go to the Application Settings section in the Auth0 dashboard and make sure that Allowed Callback URLs contains the following value:

      + +
      https://@@account.namespace@@/mobile
      + +

      Also, if you are testing your application locally, make sure to add your local URL as an Allowed Callback URL and the following as an Allowed Origin (CORS):

      + +
      file://*
      + +
      + +### 2. Adding the Auth0 dependencies + +Add the following dependencies to the `bower.json` and run `bower install`: + +````json +"dependencies" : { + "auth0-angular": "3.*", + "a0-angular-storage": ">= 0.0.6", + "angular-jwt": ">= 0.0.4" +}, +``` + +### 3. Add the references to the scripts in the `index.html` + +````html + + + + + + + + +``` + +### 4. Add `InAppBrowser` plugin + +You must install the `InAppBrowser` plugin from Cordova to be able to show the Login popup. For that, just run the following command: + +````bash +ionic plugin add org.apache.cordova.inappbrowser +``` + +and then add the following configuration to the `config.xml` file: + +````xml + + + + +``` + +### 5. Add the module dependency and configure the service + +Add the `auth0`, `angular-storage` and `angular-jwt` module dependencies to your angular app definition and configure `auth0` by calling the `init` method of the `authProvider` + +````js +// app.js +angular.module('starter', ['ionic', + 'starter.controllers', + 'starter.services', + 'auth0', + 'angular-storage', + 'angular-jwt']) +.config(function($stateProvider, $urlRouterProvider, authProvider, $httpProvider, + jwtInterceptorProvider) { + + + $stateProvider + // This is the state where you'll show the login + .state('login', { + url: '/login', + templateUrl: 'templates/login.html', + controller: 'LoginCtrl', + }) + // Your app states + .state('dashboard', { + url: '/dashboard', + templateUrl: 'templates/dashboard.html', + data: { + // This tells Auth0 that this state requires the user to be logged in. + // If the user isn't logged in and he tries to access this state + // he'll be redirected to the login page + requiresLogin: true + } + }) + + ... + + authProvider.init({ + domain: '<%= account.namespace %>', + clientID: '<%= account.clientId %>', + loginState: 'login' + }); + + ... + +}) +.run(function(auth) { + // This hooks all auth events to check everything as soon as the app starts + auth.hookEvents(); +}); +``` + + +### 6. Let's implement the login + +Now we're ready to implement the Login. We can inject the `auth` service in any controller and just call `signin` method to show the Login / SignUp popup. +In this case, we'll add the call in the `login` method of the `LoginCtrl` controller. On login success, we'll save the user profile, token and [refresh token](@@base_url@@/refresh-token) into `localStorage` + +````js +// LoginCtrl.js +function LoginCtrl(store, $scope, $location, auth) { + $scope.login = function() { + auth.signin({ + authParams: { + scope: 'openid offline_access', + device: 'Mobile device' + } + }, function(profile, token, accessToken, state, refreshToken) { + // Success callback + store.set('profile', profile); + store.set('token', token); + store.set('refreshToken', refreshToken); + $location.path('/'); + }, function() { + // Error callback + }); + } +} +``` + +````html + + + + +``` + +> Note: there are multiple ways of implementing login. What you see above is the Login Widget, but if you want to have your own UI you can change the ` +``` + +1.4. Edit the `www/config.xml` file to include your Auth0 domain in the list of allowed origins: + +```xml + +``` + +### 2. Setting up the callback URL in Auth0 + +
      +

      Go to the Application Settings section in the Auth0 dashboard and make sure that Allowed Callback URLs contains the following value:

      + +
      https://@@account.namespace@@/mobile
      +
      + +### 3. Integration +There are three options to do the integration: + +1. Using the [Auth0 Login Widget](login-widget2) inside an InAppBrowser control (this is the simplest with only a few lines of code required). +2. Creating your own UI (more work, but higher control the UI and overall experience). +3. Using specific user name and password. + +#### Option 1: Authentication using Login Widget + +To start with, we'd recommend using the __Login Widget__. Here is a snippet of code to copy & paste on your project: + +```javascript +var auth0 = new Auth0Client( + "@@account.namespace@@", + "@@account.clientId@@"); + +auth0.login(function (err, result) { + if (err) return err; + /* + Use result to do wonderful things, e.g.: + - get user email => result.profile.email + - get facebook/google/twitter/etc access token => result.profile.identities[0].access_token + - get Windows Azure AD groups => result.profile.groups + - etc. + */ +}); +``` + +![](//cdn.auth0.com/docs/img/phonegap.auth0client.png) + +#### Option 2: Authentication with your own UI + +If you know which identity provider you want to use, you can add a `connection` parameter and the user will be sent straight to the specified `connection`: + +```javascript +auth0.login({ connection: "google-oauth2" }, function (err, result) { + if (err) return err; + /* + Use result to do wonderful things, e.g.: + - get user email => result.profile.email + - get facebook/google/twitter/etc access token => result.profile.identities[0].access_token + - get Windows Azure AD groups => result.profile.groups + - etc. + */ +}); +``` + +> connection names can be found on Auth0 dashboard. E.g.: `facebook`, `linkedin`, `somegoogleapps.com`, `saml-protocol-connection`, etc. + +#### Option 3: Authentication with specific user name and password + +```javascript +auth0.login({ + connection: "sql-azure-database", + username: "jdoe@foobar.com", + password: "1234" +}, +function (err, result) { + if (err) return err; + /* Use result to do wonderful things */ +}); +``` + +## Accessing user information + +The `Auth0User` has the following properties: + +* `profile`: returns a JSON object containing all available user attributes (e.g.: `user.profile.email`). +* `idToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity. +* `auth0AccessToken`: the `access_token` that can be used to access Auth0's API. You would use this for example to [link user accounts](link-accounts). + +## Download the sample + +Browse the sample on GitHub from [here](https://github.com/auth0/phonegap-auth0-sample). + + +**Congratulations!** diff --git a/docs/ping7.md b/docs/ping7.md index 6f355266..a124c610 100644 --- a/docs/ping7.md +++ b/docs/ping7.md @@ -6,100 +6,101 @@ layout: doc.nosidebar Most options are the default values. You will just need to press __Next__ in most screens. If metadata import fails for some reason, keep these values at hand. These are the most important configuration parameters: -* __EntityID:__ `urn:auth0:@@account.tenant@@` +* __EntityID:__ `urn:auth0:@@account.tenant@@:@@connectionName@@` * __Assertion Consumer Service URL:__ `https://@@account.namespace@@/login/callback` * __Logout URL:__ `https://@@account.namespace@@/logout` * __HTTP-Redirect__ binding for SAML Request * __HTTP-POST__ binding for SAML Response +> If you want **IdP-Initiated SSO**, please make sure to include the connection parameter in the Assertion Consumer Service URL: `https://@@account.namespace@@/login/callback?connection=@@connectionName@@` ###1. Download Auth0 Metadata File -Download the metadata file from [here](https://@@account.namespace@@/samlp/metadata). This will be used in [step 3](ping7#3) and it is used to automatically import information about your partner. +Download the metadata file from [here](https://@@account.namespace@@/samlp/metadata?connection=@@connectionName@@). This will be used in [step 3](ping7#3) and it is used to automatically import information about your partner. ###2. Create a new __SP Connection__ Login to _PingFederate_ as an administrator (the URL would be something like __https://{your ping server}:{port}/pingfederate/app__). Select __Create New__ from the __SP Connections__ section on the left: -![](img/ping-1.png) +![](//cdn.auth0.com/docs/img/ping-1.png) ###3. Configure the __SP Connection__ Select the __Browser SSO Profles__ as the __Connection Type__: -![](img/ping-2.png) +![](//cdn.auth0.com/docs/img/ping-2.png) Select __Browser SSO__ as the __Connection Options__: -![](img/ping-3.png) +![](//cdn.auth0.com/docs/img/ping-3.png) -Upload the [__metadata file__](https://@@account.namespace@@/samlp/metadata) you downloaded in step 1. The __Entity ID__, __Connection Name__ and the __Base URL__ will be automatically completed based on the information from the metadata file. You can also complete other relevant information from your partner: +Upload the [__metadata file__](https://@@account.namespace@@/samlp/metadata?connection=@@connectionName@@) you downloaded in step 1. The __Entity ID__, __Connection Name__ and the __Base URL__ will be automatically completed based on the information from the metadata file. You can also complete other relevant information from your partner: -![](img/ping-4.png) +![](//cdn.auth0.com/docs/img/ping-4.png) ###4. Configure __Browser SSO__ Select __SP-Initiated SSO__ and __SP-Initiated SLO__ in __SAML Profiles__: -![](img/ping-5.png) +![](//cdn.auth0.com/docs/img/ping-5.png) Move on to the __Assertion Creation__ section and click on __Configure Assertion__: -![](img/ping-6.png) +![](//cdn.auth0.com/docs/img/ping-6.png) -You can leave all defaults for the next two screens. Move on to the __IdP Adapter Mapping__ section: +You can leave all defaults for the next two screens. Move on to the __IdP Adapter Mapping__ section: -![](img/ping-7.png) -![](img/ping-8.png) +![](//cdn.auth0.com/docs/img/ping-7.png) +![](//cdn.auth0.com/docs/img/ping-8.png) The last step is to add an __IdP Adapter Mapping__. This is where users will actually be authenticated. Likely, you already have one configured in your _PingFederate_ installation. Select one, or add a new one. In principle, [Auth0](http://auth0.com) only requires the __NameIdentifier__ claim. All other attributes will be passed further to the end application. -![](img/ping-9.png) +![](//cdn.auth0.com/docs/img/ping-9.png) -In this example, we are just using the `username` from a simple HTML IdP Adapter. No __Issuance Criteria__ are being used. +In this example, we are just using the `username` from a simple HTML IdP Adapter. No __Issuance Criteria__ are being used. -![](img/ping-10.png) +![](//cdn.auth0.com/docs/img/ping-10.png) ###5. Configure __Protocol Settings__ All important values for __Protocol Settings__ are imported from the __Metadata File__. You should see the __Assertion Consumer Service URL__: -![](img/ping-11.png) +![](//cdn.auth0.com/docs/img/ping-11.png) And the Sign-Out URLs. Just click __Next__ to the __Allowable SAML Bindings__ section. -![](img/ping-12.png) +![](//cdn.auth0.com/docs/img/ping-12.png) Leave __POST__ and __Redirect__ enabled: -![](img/ping-13.png) +![](//cdn.auth0.com/docs/img/ping-13.png) Make sure __SAML Assertion__ is always signed and move on to the end of this section. -![](img/ping-14.png) +![](//cdn.auth0.com/docs/img/ping-14.png) ###6. Configure __Credentials__ This is the last step for configuring __Browser SSO__. On __Digital Signature Settings__, select your signing certificate and make sure you check the option to include it in the `` element: -![](img/ping-15.png) +![](//cdn.auth0.com/docs/img/ping-15.png) -The last two options to configure are the certificate used to sign incoming requests. Auth0 will not sign `SAMLRequests` by default. For some reason, there's no way around this setting. [Download the Auth0 certificate](https://@@account.tenant@@.auth0.com/pem) and upload it here. +The last two options to configure are the certificate used to sign incoming requests. Auth0 will not sign `SAMLRequests` by default. For some reason, there's no way around this setting. [Download the Auth0 certificate](https://@@account.tenant@@.auth0.com/pem) and upload it here. -![](img/ping-16.png) -![](img/ping-17.png) +![](//cdn.auth0.com/docs/img/ping-16.png) +![](//cdn.auth0.com/docs/img/ping-17.png) ###7. Activation of the __SP Connection__ In the last step, you'll see the summary of all your previous settings and an option to set is as __Active__ or __Inactive__: -![](img/ping-18.png) +![](//cdn.auth0.com/docs/img/ping-18.png) -In any case, make sure your click the button __Save__ at the bottom of the screen. +In any case, make sure your click the button __Save__ at the bottom of the screen. You are done! You should see the new SP Connection on the __Main__ screen: -![](img/ping-19.png) +![](//cdn.auth0.com/docs/img/ping-19.png) diff --git a/docs/planningcenter-clientid.md b/docs/planningcenter-clientid.md new file mode 100644 index 00000000..d698475c --- /dev/null +++ b/docs/planningcenter-clientid.md @@ -0,0 +1,8 @@ +# Obtaining a Consumer and Secret Keys for Planning Center + +To configure a connection with [Planning Center](http://get.planningcenteronline.com/api), send an e-mail to [support@planningcenteronline.com](mailto://support@planningcenteronline.com) and request a `Consumer` & `Secret` Keys. + +Please include this callback URL in the request: + + https://@@account.namespace@@/login/callback + diff --git a/docs/premium-support.md b/docs/premium-support.md new file mode 100644 index 00000000..34aaf7dd --- /dev/null +++ b/docs/premium-support.md @@ -0,0 +1,11 @@ +# Premium Support + +Auth0 offers great support for __all__ subscribers. You can email an unlimited amount of queries to our customer success team at , chat with them at [http://chat.auth0.com](http://chat.auth0.com), and learn from them and the broader Auth0 community at [https://ask.auth0.com](https://ask.auth0.com). + +Auth0 offers a __Premium Support__ option, which we recommend for all subscribers using Auth0 in production environments: + +![](https://docs.google.com/drawings/d/1--ZQ8r9B6O8Kii1CKFjPOpJmGBiHlbAfM_mlaj6ySX0/pub?w=744&h=346) + + + +If you have specific support requirements or are interested in the __24/7 Support__ option, please contact us at . \ No newline at end of file diff --git a/docs/pricing-per-app-per-connection.md b/docs/pricing-per-app-per-connection.md new file mode 100644 index 00000000..215eb628 --- /dev/null +++ b/docs/pricing-per-app-per-connection.md @@ -0,0 +1,33 @@ +# Understanding Auth0 Pricing per App and per Connection + +### Introduction +In addition to the IdP type, the number of active users and the addons you select, Auth0 pricing takes into consideration the number of apps you connect to the Auth0 platform as well as the number of connections you establish with your end customers. This article provides additional information about Auth0 per app and per connection pricing. + +### Simple scenario: 1 App, 1 Connection +A simple yet very common Auth0 scenario is when an Auth0 customer builds 1 app or api and enables users to authenticate to it via a 1 connection. +In the picture below, an Auth0 customer built and angular.js app and created an active directory connection, allowing active users to authenticate themselves using their corporate credentials, whether they are in the office or on the road. + +![]( +https://docs.google.com/drawings/d/1iZHRuRj7w3jjt4Zn1pNk6T7kxKRbNaii5l8gbF8Pbg0/pub?w=732&h=108) + +In this simple example, Auth0 would charge the customer based on the fact that it is an enterprise connection (as opposed to a social or database connection), the number of monthly active users authenticating to the app as well as any addons (e.g. premium support) selected by the customer. + +The self service model [https://auth0/pricing](https://auth0/pricing) assumes this scenario. + +### Corporate scenario: Multiple apps, 1 Connection +Frequently though, Auth0 customers use the platform for multiple applications. It is important to note that we consider an app different from another when it offers different functionalities. For example an inventory management app and an appointment booking app would be considered separate apps. However, the inventory management website, the inventory management iOS companion app and the inventory management android app would all be considered a single app in the context of Auth0 pricing. + +![](https://docs.google.com/drawings/d/16FjBPdKd0R0wDiIyHAGgbRMaTvIPdiW4l4soxV7diVo/pub?w=776&h=274) + +In this case, Auth0 would charge the customer, based on the number of monthly active users per ___each___ app. In a self service model, it would be 5 different subscriptions, one for each app based on the active user count for each app. In addition to that, for Box and Zendesk the "3rd Party App SSO" addon will have to be switched on. In other words, active users __cannot__ be pooled across apps in the self service model. +We offer __pooled users__ across multiple apps pricing as well as __unlimited__ app pricing through our custom agreements. Please contact for more details. + +### SaaS or On Prem ISV Model: 1 app, multiple connections + +Another common scenario is "the ISV scenario". In this scenario, the Auth0 customer, usually a SaaS or On Prem ISV, builds an application that is used by multiple organizations. Here is an example. One of our customers offers an online learning platform, every time they sign a new customer of theirs, they add an AD, LDAP, Google Apps (whichever is most relevant) connection in Auth0, allowing this new customer’s users to have single sign on with the online training platform. We consider this approach as “multiple connections” in the context of Auth0 pricing. +On the other hand, when multiple connections are setup to enable users from the same organization, we would consider this as 1 connection in the context of Auth0 pricing. For example, if 1 LDAP connection is created for the accounting division and 1 AD connection for the marketing department of the same organization, we would consider this as 1 connection from a pricing perspective. + +![](https://docs.google.com/drawings/d/1Zx2ZDvW4sIfZ0Vz8ObTJlVlQL2CkVpNuPvjySJ5tZd8/pub?w=759&h=285) + +In case of multiple connections, Auth0 would charge the customer, based on the number of monthly active users per ___each___ connection. In a self service model, it would be 5 different subscriptions, one for each end customer connection. +We offer __pooled user__ across multiple connections as well as __unlimited__ connection pricing in our custom orders. Please contact for more details. diff --git a/docs/protocols.md b/docs/protocols.md index 871ade1e..85d20eca 100644 --- a/docs/protocols.md +++ b/docs/protocols.md @@ -13,7 +13,7 @@ This protocol is best suited for web sites that need: > In the literature you might find this flow refered to as __Authorization Code Grant__. The full spec of this flow is [here](http://tools.ietf.org/html/rfc6749#section-4.1). -![](img/protocols-oauth-code.png) +![](https://docs.google.com/drawings/d/1RZYKxbO5LM3fBhL8hs5-wefUkwDgPo-lOuoHWBdc0RI/pub?w=793&h=437) ### 1. Initiation @@ -27,7 +27,11 @@ Someone using a browser hits a protected resource in your web app (a page that r The `redirect_uri` __must__ match what is defined in your [settings](@@uiURL@@/#/settings) page. `http://localhost` is a valid address and Auth0 allows you to enter many addresses simultaneously. -> Optionally, you can specify a `scope` parameter. The only supported values today are: `openid` and `openid profile`. If you do add a `scope` parameter (with the supported values), Auth0 will return a [JsonWebToken](jwt) in addition to the `access_token` in step #3 (below). +Optionally you can specify a `scope` parameter. There are various possible values for `scope`: + +* `scope: 'openid'`: _(default)_ It will return, not only the `access_token`, but also an `id_token` which is a Json Web Token (JWT). The JWT will only contain the user id (`sub` claim). +* `scope: 'openid profile'`: If you want the entire user profile to be part of the `id_token`. +* `scope: 'openid {attr1} {attr2} {attrN}'`: If you want only specific user's attributes to be part of the `id_token` (For example: `scope: 'openid name email picture'`). --- @@ -37,19 +41,21 @@ Auth0 will start the authentication against the identity provider configured wit The visible part of this process is that the user is redirected to the identity provider site. +> When using Auth0's built-in user store (created through __Connections -> Database -> New__), there's no redirection. Auth0 in this case is the identity provider. + --- ### 3. Getting the Access Token -Upon successful authentication, the user will eventually return to your web site with a URL that will look like: +Upon successful authentication, the user will eventually return to your web site with a URL that will look like (steps 3 & 4 in the diagram above): http://CALLBACK/?code=AUTHORIZATION_CODE&state=OPAQUE_VALUE `CALLBACK` is the URL you specified in step #2 (and configured in your settings). `state` should be the same value you sent in step #1. -Your web site will then call Auth0 again with a request to obtain an "Access Token" that can be further used to interact with the Auth0 [API](api-reference). +Your web site will then call Auth0 again with a request to obtain an "Access Token" that can be further used to interact with Auth0's API. -To get an Access Token, you would send a POST request to the token endpoint in Auth0. You will need to send the `code` obtained before along with your `clientId` and `clientSecret`. +To get an Access Token, you would send a POST request to the token endpoint in Auth0. You will need to send the `code` obtained before along with your `clientId` and `clientSecret` (step 5 in the diagram). POST https://@@account.namespace@@/oauth/token @@ -62,11 +68,14 @@ If the request is successful, you will get a JSON object with an `access_token`. #####Sample Access Token Response: { - "access_token":"2YotnFZFEjr1zCsicMWpAA", + "access_token":".....Access Token.....", "token_type":"bearer", + "id_token":"......JWT......" } -> Adding a `scope=openid` parameter to the request sent to the `authorize` endpoint as indicated above, will result in an additional property called `id_token`. This is a [JsonWebToken](jwt). +> Adding a `scope=openid` parameter to the request sent to the `authorize` endpoint as indicated above, will result in an additional property called `id_token`. This is a [JsonWebToken](jwt). You can control what properties are returned in the JWT (e.g. `scope=openid name email`). + +Notice that the call to exchange the `code` for an `access_token` is __server to server__ (usually your web backend to Auth0). The system initiating this call has to have access to the public internet to succeed. A common source of issues is the server running under an account that doesn't have access to internet. ## OAuth for Native Clients and JavaScript in the browser @@ -74,7 +83,7 @@ This protocol is best suited for mobile native apps and javascript running in a > The full spec of this protocol can be found [here](http://tools.ietf.org/html/rfc6749#section-4.2) and it is refered to as the __Implicit Flow__. -![](img/protocols-oauth-implicit.png) +![](https://docs.google.com/drawings/d/1S_p6WdsOno50aKlr08SueWL25a86l86e8CQLMyDQx_8/pub?w=752&h=282) ### 1. Initiation @@ -84,7 +93,7 @@ The client requests authorization to Auth0 endpoint: The `redirect_uri` __must__ match one of the addresses defined in your [settings](@@uiURL@@/#/settings) page. -> Optionally, you can specify a `scope` parameter. The only supported values today are: `openid` and `openid profile`. If you do add a `scope` parameter (with the supported values), Auth0 will return a [JsonWebToken](jwt) in addition to the `access_token` in step #3 (below). +> Adding a `scope=openid` parameter to the request sent to the `authorize` endpoint as indicated above, will result in an additional property called `id_token`. This is a [JsonWebToken](jwt). You can control what properties are returned in the JWT (e.g. `scope=openid name email`). ### 2. Authentication @@ -100,8 +109,9 @@ Optionally (if `scope=openid` is added in the authorization request): https://CALLBACK#access_token=ACCESS_TOKEN&id_token=JSON_WEB_TOKEN -Clients typically extract the URI fragment with the __Access Token__ and follow the redirection. The client code will then interact with other endpoints using the token in the fragment. +Clients typically extract the URI fragment with the __Access Token__ and cancel the redirection. The client code will then interact with other endpoints using the token in the fragment. +> Note that tokens can become large and under certain conditions the URL might be truncated (e.g. some browsers have URL length limitations). Be especially careful when using the `scope=openid profile` that will generate a JWT with the entire user profile in it. You can define specific attributes to return in the JWT (e.g. `scope=openid email name`). ## OAuth Resource Owner Password Credentials Grant @@ -111,25 +121,26 @@ This endpoint is used by clients to obtain an access token (and optionally a [Js It receives a `client_id`, `client_secret`, `username`, `password` and `connection`. It validates username and password against the connection (if the connection supports active authentication) and generates an access_token. - POST /oauth/ro HTTP/1.1 - Host: {auth0_domain}.auth0.com + POST /oauth/ro HTTP/1.1 + Host: @@account.namespace@@ Content-Type: application/x-www-form-urlencoded - grant_type=password&username=johndoe&password=abcdef&client_id=...&client_secret=...&connection=... + grant_type=password&username=johndoe&password=abcdef&client_id=@@account.clientId@@&connection=YOUR CONNECTION -Currently, Auth0 implements the following connections: +Currently, Auth0 implements the following connections for a resource owner grant: * google-oauth2 * google-apps -* database connections +* AD/LDAP +* Database connections -> Note: For Google authentication we are relaying on an endpoint that is marked as deprecated, so use it at your own risk. The user might get a warning saying that someone has logged in from another IP address and will have to complete a captcha challenge allowing the login to happen. +> Note: For Google authentication we are relying on an endpoint that is marked as deprecated, so use it at your own risk. The user might get a warning saying that someone has logged in from another IP address and will have to complete a captcha challenge allowing the login to happen. -As optional parameter, you can include scope=openid. It will return, not only the *access_token*, but also an *id_token* which is a Json Web Token ([JWT](jwt)). +As optional parameter, you can include scope=openid. It will return, not only the *access_token*, but also an *id_token*. This is a [JsonWebToken](jwt). You can control what properties are returned in the JWT (e.g. `scope=openid name email`). `scope=openid profile` will return the entire user profile. #### Sample Request - curl --data "grant_type=password&username=johndoe&password=abcdef&client_id=...&client_secret=...&connection=...&scope=openid" https://{auth0_domain}.auth0.com/oauth/ro + curl --data "grant_type=password&username=johndoe&password=abcdef&client_id=@@account.clientId@@&connection=&scope=openid" https://@@account.namespace@@/oauth/ro ### Login Response In response to a login request, Auth0 will return either an HTTP 200, if login succeeded, or an HTTP error status code, if login failed. @@ -172,4 +183,33 @@ A failure response will contain error and error_description fields. "error_description": "specified strategy does not support requested operation (windowslive)" } +## OAuth2 / OAuth1 + +These protocols are implemented mostly when interacting with well-known [identity providers](identityproviders). Most of the __social identity providers__ implement one or the other. The default protocol in Auth0 is OpenID Connect (see above). + +`scopes` for each identity provider can be configured on the Auth0 dashboard, but these can also be sent on-demand on each authentication request through the the `connection_scopes` parameter. (See [this topic](@@base_url@@/login-widget2#8) for more details) + +## WS-Federation + +WS-Federation is supported both for apps (e.g. any WIF based app) and for identity providers (e.g. ADFS or ACS). + +###For apps +All registered apps in Auth0 get a WS-Fed endpoint of the form: + + https://@@account.namespace@@/@@account.clientId@@/wsfed + +The metadata endpoint that you can use to configure the __Relying Party__: + + https://@@account.namespace@@/@@account.clientId@@/FederationMetadata/2007-06/FederationMetadata.xml + +All options for WS-Fed are available under the [advanced settings](https://app.auth0.com/#/applications/@@account.clientId@@/settings) for an App. + +Claims sent in the SAML token, as well as other lower level settings of WS-Fed & SAML-P can also be configured with the `samlConfiguration` object through [rules](saml-configuration). + +###For IdP +If you are connecting a WS-Fed IdP (e.g. ADFS, Azure ACS and IdentityServer are examples), then the easiest is to use the __ADFS__ connection type. Using this you just enter the server address. Auth0 will probe for the __Federation Metadata__ endpoint and import all the required parameters: certificates, URLs, etc. + +> You can also upload a Federation Metadata file. + +If a primary and a secondary certificates are present in the __Federation Metadata__, then both would work. Connection parameters can be updated anytime (by clicking on __Edit__ and __Save__). This allows simple certificate rollover. diff --git a/docs/rails-tutorial.md b/docs/rails-tutorial.md deleted file mode 100644 index 7ed3b409..00000000 --- a/docs/rails-tutorial.md +++ /dev/null @@ -1,80 +0,0 @@ -# Using Auth0 with Ruby On Rails - -This tutorial explains how to integrate Auth0 with a Ruby on Rails application. - -## Tutorial - -### 1. Install Auth0 gem - -Use RubyGems to install the **auth0** gem, running the command: - -``` -gem install auth0 -``` - -> This gem is essentially an [Omniauth Strategy](https://github.com/intridea/omniauth/wiki/Strategy-Contribution-Guide). - -### 2. Setting up the callback URL in Auth0 - -
      -

      After authenticating the user on Auth0, we will do a GET to a URL on your web site. For security purposes, you have to register this URL on the Application Settings section on Auth0 Admin app.

      - -
      http://localhost:port/auth/auth0/callback
      -
      - -### 3. Initialize the auth0 gem - -Add the `auth0.rb` file under the `config/initializers` folder with the following settings: - - Rails.application.config.middleware.use OmniAuth::Builder do - provider :auth0, '@@account.clientId@@', '@@account.clientSecret@@', '@@account.namespace@@' - end - -### 4. Initialize the auth0 strategy in your app - -Create the callback controller - - rails generate controller callback store failure - -Open the `callback_controller.rb` under the `app/controllers` folder and implement the methods `store` (used to store the user profile in session), and `failure` (to display error messages): - - class CallbackController < ApplicationController - def store - session[:userinfo] = request.env['omniauth.auth'] - redirect_to user_index_path - end - - def failure - @error_msg = request.params['message'] - end - end - -Update the callback routes in the `routes.rb` under `config` folder: - - match "auth/auth0/callback" => "callback#store" - match "auth/failure" => "callback#failure" - -### 5. Triggering login manually or integrating the Auth0 widget - -@@sdk@@ - -### 6. Accessing user information - -Once the user successfully authenticates and returns to the application, you can retrieve his/her profile attributes through the `session[:userinfo]` stored in `CallbackController` - - class UserController < ApplicationController - def index - @user = session[:userinfo] - end - end - -The userinfo includes these: `uid`, `name`, `email`, `nickname` and `image`. - -
      -

      UID

      - <%= @user.uid %> -
      - -OmniAuth will always return a hash of information after authenticating with an external provider in the Rack environment under the key `omniauth.auth`. This information is meant to be as normalized as possible, so the schema below will be filled to the greatest degree available given the provider upon authentication. For more information about the user profile [read this](https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema), and read [Auth0's normalized user profile](user-profile). - -**Congratulations!** diff --git a/docs/refresh-token.md b/docs/refresh-token.md new file mode 100644 index 00000000..5639821e --- /dev/null +++ b/docs/refresh-token.md @@ -0,0 +1,81 @@ +# Refresh Tokens + +__Refresh tokens__ are special kinds of tokens that your app can use to get renewed `id_token` ([JWT](@@base_url@@/jwt)) **at any time**. A __refresh token__ must be securely stored in your app. + +## Introduction + +The response of an initial authentication request results in an `id_token` being issued by Auth0. You use this token to authenticate calls to your secured API. + +Among other security measures, like signing, every `id_token` has an expiration. By default this is set to 10 hours since issue time, but it can be changed. + +Applications that are installed on a device such as a computer or a smartphone among others, might often want to avoid asking the user to enter credentials each time a token expires. + +A `refresh_token` is an artifact that allows the application to request Auth0 to issue a new `id_token`. This works as long as the __refresh token__ is not revoked in the server. + +Think of a __refresh token__ as a permission to renew tokens indefinitely, until you cancel that permission (which happens when it is __revoked__). When that happens, user will have to log in again no matter what. + +## Security considerations + +Because a __refresh token__ never expires, it is important to provide admins of apps ability to revoke access. Auth0 allows this process to happen both from the dashboard and programmatically through an API. + +__Refresh tokens__ can be issued and revoked for each combination of __app__, __user__ and __device__. +To revoke a __refresh token__, you can call the **[revoke refresh token](@@base_url@@/api#delete--api-users--user_id--refresh_tokens--refresh_token-)** endpoint: + +```` +DELETE https://@@account.namespace@@/api/users//refresh_tokens/ + +{ + "Authorization": "Bearer ", +} + +``` + +## How it all works + +The process to log the user in is the same as [explained in the sequence diagram page](@@base_url@@/sequence-diagrams). The difference is that when calling the **`authorize`** endpoint, you must include the **`offline_access`** `scope`. For example: + +```` +GET https://@@account.namespace@@/authorize/? + response_type=token + &client_id=@@account.clientId@@ + &redirect_uri=YOUR_CALLBACK_URL + &state=VALUE_THAT_SURVIVES_REDIRECTS + &scope=openid%20offline_access +``` + +After the user authenticates with the IdP, Auth0 will redirect the user to the callback URL as usual. The complete URL will look like: + +```` +GET YOUR_CALLBACK_URL# + access_token=2YotnF..........1zCsicMWpAA + &id_token=......Json Web Token...... + &state=VALUE_THAT_SURVIVES_REDIRECTS + &refresh_token=....The refresh token.... +``` + +This is the same as before but it will now contain a `refresh_token` parameter. + +> Notice the token is sent back in the URL because `authorize` request in this example uses `response_type=token`. + +Every time you need to get a new `id_token`, you can call the **[Delegation endpoint](@@base_url@@/auth-api#!#post--delegation)** + +```` +POST https://@@account.namespace@@/delegation +Content-Type: 'application/json' +{ + "client_id": "@@account.clientId@@", + "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", + "refresh_token": "your_refresh_token", + "api_type": "app" +} +``` + +## Using it with SDKs + +Libraries like `auth0.js`, `auth0-widget.js` or `auth0-angular.js` all include support to get a `refresh_token` and also renewed `id_tokens`. + +You can read more about them in the following links: + +* [Getting it with Auth0.js](https://github.com/auth0/auth0.js#login) and [using it to get a new id_token](https://github.com/auth0/auth0.js#refresh-token) +* [Getting it with Auth0-widget.js](https://github.com/auth0/widget#show-widget). Using it is through Auth0.js +* [Getting and using it with Auth0-angular.js](https://github.com/auth0/auth0-angular/blob/master/docs/refresh-token.md) diff --git a/docs/renren-clientid.md b/docs/renren-clientid.md new file mode 100644 index 00000000..9c330fa1 --- /dev/null +++ b/docs/renren-clientid.md @@ -0,0 +1,25 @@ +# Obtaining an API Key and Secret Key for RenRen + +To configure a RenRen OAuth2 connection you will need to register your Auth0 tenant on their [integration portal](http://app.renren.com/developers). + +##1. Log in into the integration portal and register a new App: + +![](//cdn.auth0.com/docs/img/renren-register-1.png) + +--- + +##2. Enter app information and callback URL: + +Use the following value for the callback URL: + + https://@@account.namespace@@/login/callback + +![](//cdn.auth0.com/docs/img/renren-register-2.png) + +--- + +##3. Get API Key and Secret Key: + +Once the app is registered, enter the new API Key and Secret Key in Auth0's RenRen connection: + +![](//cdn.auth0.com/docs/img/renren-register-3.png) diff --git a/docs/rules.md b/docs/rules.md index 1823b9d4..c7435c9d 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -1,10 +1,10 @@ # Rules -Rules are code snippets written in JavaScript that are executed as part of the authentication pipeline in Auth0. This happens every time a user authenticates to an application. __Rules__ enable very powerful customizations and extensions to be easily added to Auth0. +Rules are code snippets written in JavaScript that are executed as part of the authentication pipeline in Auth0. This happens every time a user authenticates to an application. __Rules__ enable very powerful customizations and extensions to be easily added to Auth0. -![](img/rules-pipeline.png) +![](https://docs.google.com/drawings/d/16W_hTS_u2CeDFXkD2PlfituFl7b74EQ6HE_XYn3TdD0/pub?w=891&h=283) -An App initiates an authentication request to Auth0 (__Step 1__), Auth0 routes the request to an Identity Provider through a configured connection (__Step 2__). The user authenticates successfuly (__Step3__), the `user` object that represents the logged in user is the passed through the rules pipeline and returned to the app (__Step 4__). +An App initiates an authentication request to Auth0 (__Step 1__), Auth0 routes the request to an Identity Provider through a configured connection (__Step 2__). The user authenticates successfuly (__Step3__), the `user` object that represents the logged in user is passed through the rules pipeline and returned to the app (__Step 4__). Rules run on __Step 4__. Here are a few examples. You could: @@ -13,6 +13,7 @@ Here are a few examples. You could: * Normalize attributes from different providers besides to what we provide out of the box. * Reuse information from existing databases or APIs in migration scenarios. * Keep a white-list of users in a file and deny access based on email. +* Have counters or other persisted information. __Auth0 Rules__ are implemented in JavaScript. Which means you don't have to learn an esoteric DSL. They run in their own sandbox to protect the core of Auth0's runtime. Even if you make a mistake and your code ends up in a tight loop for example, everything else will work just fine. @@ -28,16 +29,32 @@ This rule will add a `hello` attribute to all users authenticating through any p callback(null, user, context) } -> **HINT**: You can try the rule while editing and you can see the output and any `console.log` output. Useful for debugging ![](img/rules.png) +> **HINT**: You can try the rule while editing and you can see the output and any `console.log` output. Useful for debugging ![](//cdn.auth0.com/docs/img/rules.png) A __Rule__ takes the following arguments: -* `user`: the user object as it comes from the identity provider. -* `context`: an object containing contextual information of the current authentication transaction. It has the following properties: - * `clientID`: the client id of the application the user is logging in to. - * `clientName`: the name of the application (as defined on the dashboard). - * `connection`: the name of the connection used to authenticate the user (e.g.: `twitter` or `some-google-apps-domain`) - * `samlConfiguration`: an object that controls the behavior of the SAML and WS-Fed endpoints. Useful for advanced claims mapping and token enrichment. +#### user +The user object as it comes from the identity provider. + +#### context +An object containing contextual information of the current authentication transaction. It has the following properties: + +* `clientID`: the client id of the application the user is logging in to. +* `clientName`: the name of the application (as defined on the dashboard). +* `connection`: the name of the connection used to authenticate the user (e.g.: `twitter` or `some-google-apps-domain`) +* `connectionStrategy`: the type of connection. For social connection `connectionStrategy` === `connection`. For enterprise connections, the strategy will be `waad` (Windows Azure AD), `ad` (Active Directory/LDAP), `auth0` (database connections), etc. +* `jwtConfiguration`: an object to configure how Json Web Tokens (JWT) will be generated: + * `lifetimeInSeconds`: expiration of the token. + * `scopes`: predefined scopes values (e.g.: `{ 'images': ['picture', 'logo'] }` this scope value will request access to the picture and logo claims). +* `protocol`: the authentication protocol. Possible values: `oidc-basic-profile` (most used, web based login), `oidc-implicit-profile` (used on mobile devices and single page apps), `oauth2-resource-owner` (user/password login typically used on database connections), `samlp` (SAML protocol used on SaaS apps), `wsfed` (WS-Federation used on Microsoft products like Office365), `wstrust-usernamemixed` (WS-trust user/password login used on CRM and Office365), and `delegation` (when calling the [Delegation endpoint](@@base_url@@/auth-api#delegated)). +* `request`: an object containing useful information of the request. It has the following properties: + * `query`: querystring of the login transaction sent by the application + * `body`: the body of the POST request on login transactions used on `oauth2-resource-owner` or `wstrust-usernamemixed` protocols. + * `userAgent`: the user-agent of the client that is trying to log in. + * `ip`: the originating IP address of the user trying to log in. +* `samlConfiguration`: an object that controls the behavior of the SAML and WS-Fed endpoints. Useful for advanced claims mapping and token enrichment (only available for `samlp` and `wsfed` protocol). +* `stats`: an object containing specific user stats, like `stats.loginsCount`. + > It is important to call the `callback` function which takes the `user` and `context` modified, otherwise the script will timeout (this is because of the async nature of node.js). @@ -51,14 +68,14 @@ Here are some other common rules: user.roles = []; // only johnfoo is admin if (user.email === 'johnfoo@gmail.com') user.roles.push('admin'); - + // all users are guest user.roles.push('guest'); - + callback(null, user, context); } -All authenticated users will get a __guest__ role, but `johnfoo@gmail.com` will also be an __admin__. +All authenticated users will get a __guest__ role, but `johnfoo@gmail.com` will also be an __admin__. John's `user` object at the beginning of the rules pipeline will be: @@ -83,9 +100,9 @@ After the rule executes, the output and what the application will receive is the email: "johnfoo@gmail.com", family_name: "Foo", user_id: "google-oauth2|103547991597142817347", - + ... other props ... - + roles: ["guest", "admin"] // NEW PROPERTY ADDED BY THE RULE } @@ -97,118 +114,49 @@ In addition to adding and removing properties from the user object, you can retu if (user.roles.indexOf('admin') === -1) { return callback(new UnauthorizedError('Only admins can use this')); } - + callback(null, user, context); } This will cause a redirect to your callback url with an `error` querystring parameter with the message you set. e.g.: `https://yourapp.com/callback?error=Only%20admins%20can%20use%20this` -> **HINT**: For prototypes and small apps, you could put a file in a cloud storage (e.g. Dropbox), get a shared link, make a request from the rule, and use the response to let the user enter or not the app, add some attributes, etc. - -### Lookup user information from a database and add new properties - -This __Rule__ will query SQL Server database and retrieve all roles associated with a user. Because `user` is a Json object you can add anything, with any structure (e.g. arrays, complex types, etc.) - - function (user, context, callback) { - getRoles(user.email, function(err, roles) { - if (err) return callback(err); - - user.roles = roles; - - callback(null, user, context); - }); - - // Queries a table by e-mail and returns associated 'Roles' - function getRoles(email, done) { - var connection = sqlserver.connect(connection_info); - - var query = "SELECT Email, Role " + - "FROM dbo.Role WHERE Email = @email"; - - connection.on('connect', function (err) { - if (err) return done(new Error(err)); - - var request = new sqlserver.Request(query, function (err, rowCount, rows) { - if (err) return done(new Error(err)); - - var roles = rows.map(function (row) { - return row[1].value; - }); - - done(null, roles); - }); - - request.addParameter('email', sqlserver.Types.VarChar, email); - - connection.execSql(request); - }); - } - } - -> **HINT**: Make sure when you call an external endpoint to open your firewall/ports to our IP address which you can find it in the rules editor. This happens when you query SQL Azure for example. - -## Query a Web Service - -You can query a SOAP Web Service using `request`, parse the response and add properties to the user. - - function (user, context, callback) { - getRoles(user.email, function(err, roles) { - if (err) return callback(err); - - user.roles = roles; - - callback(null, user, context); - }); - - function getRoles(callback) { - request.post({ - url: 'https://somedomain.com/RoleService.svc', - body: '', - headers: { 'Content-Type': 'text/xml; charset=utf-8', - 'SOAPAction': http://tempuri.org/RoleService/GetRolesForCurrentUser' } - }, function (err, response, body) { - if (err) return callback(err); - - var parser = new xmldom.DOMParser(); - var doc = parser.parseFromString(body); - var roles = xpath.select("//*[local-name(.)='string']", doc).map(function(node) { return node.textContent; }); - return callback(null, roles); - }); - } - } - -## SAML Attribute mappings - -We also expose the SAML configuration as part of the `context` object. This allows you to change how SAML Assertions are generated by Auth0. For instance, if you want to change the mappings between the [normalized profile](user-profile) and the SAML Attributes produced, you can modify the `context.samlConfiguration.mappings` property. - - function (user, context, callback) { - context.samlConfiguration.mappings = { - "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "user_id" - "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "email", - "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "name" - }; - - callback(null, user, context) - } +We have an open source repository for common rules here: -For more information about this, read [SAML Configuration](saml-configuration). + ## Available Modules The script runs in a JavaScript sandbox for security reasons. You can use the full power of the language (ECMAScript 5) and a few selected libraries. The current sandbox supports: -* [async](https://github.com/caolan/async) -* [request](https://github.com/mikeal/request) -* [sqlserver](https://github.com/pekim/tedious) -* [mongo](https://github.com/mongodb/node-mongodb-native) -* [mysql](https://github.com/felixge/node-mysql) -* [crypto](http://nodejs.org/docs/v0.8.23/api/crypto.html) -* [xmldom](https://github.com/jindw/xmldom) -* [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) -* [xpath](https://github.com/goto100/xpath) -* [bcrypt](https://github.com/ncb000gt/node.bcrypt.js) -* [pbkdf2](https://github.com/davidmurdoch/easy-pbkdf2) -* [Buffer](http://nodejs.org/api/buffer.html) - -> Looking for something not listed here? Write us to [support@auth0.com](mailto:support@auth0.com) +* [async](https://github.com/caolan/async) _(~0.1.22)_ +* [azure_storage](https://github.com/Azure/azure-storage-node) _(~0.4.1)_ +* [bcrypt](https://github.com/ncb000gt/node.bcrypt.js) _(~0.7.5)_ +* [Buffer](http://nodejs.org/docs/v0.10.24/api/buffer.html) +* [couchbase](https://github.com/couchbase/couchnode) _(~1.2.1)_ +* [crypto](http://nodejs.org/docs/v0.10.24/api/crypto.html) +* [ip](https://github.com/keverw/range_check) _(0.0.1)_ +* [jwt](https://github.com/auth0/node-jsonwebtoken) _(~0.1.0)_ +* [knex](http://knexjs.org) _(~0.6.3)_ + * The function returned by `require('knex')` is available as `Knex`. +* [lodash](https://github.com/lodash/lodash) _(~2.4.1)_ +* [mongo](https://github.com/mongodb/node-mongodb-native) _(~1.3.15)_ + * [BSON](http://mongodb.github.io/node-mongodb-native/api-bson-generated/bson.html) + * [Double](http://mongodb.github.io/node-mongodb-native/api-bson-generated/double.html) + * [Long](http://mongodb.github.io/node-mongodb-native/api-bson-generated/long.html) + * [ObjectID](http://mongodb.github.io/node-mongodb-native/api-bson-generated/objectid.html) + * [Timestamp](http://mongodb.github.io/node-mongodb-native/api-bson-generated/timestamp.html) +* [mysql](https://github.com/felixge/node-mysql) _(~2.0.0-alpha8)_ +* [pbkdf2](https://github.com/davidmurdoch/easy-pbkdf2) _(0.0.2)_ +* [pg](https://github.com/brianc/node-postgres) _(4.1.1)_ +* [pubnub](https://github.com/pubnub/javascript/tree/master/node.js) _(3.7.0)_ +* [q](https://github.com/kriskowal/q) _(~1.0.1)_ +* [querystring](http://nodejs.org/api/querystring.html) _(0.10.28)_ +* [request](https://github.com/mikeal/request) _(~2.27.0)_ +* [sqlserver](https://github.com/pekim/tedious) _(~0.1.4)_ +* [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) _(~0.2.8)_ +* [xmldom](https://github.com/jindw/xmldom) _(~0.1.13)_ +* [xpath](https://github.com/goto100/xpath) _(0.0.5)_ +* [xtend](https://github.com/Raynos/xtend) _(~1.0.3)_ + +> Looking for something not listed here? Write to us at [support@auth0.com](mailto:support@auth0.com) diff --git a/docs/saas-apps.md b/docs/saas-apps.md new file mode 100644 index 00000000..f3f940a5 --- /dev/null +++ b/docs/saas-apps.md @@ -0,0 +1,174 @@ +# Using Auth0 in SaaS, multi-tenant Apps + +> Multi-tenancy refers to a principle in software architecture where a single instance of the software runs on a server, serving multiple client-organizations (referred to as tenants). + +Let's start by enumerating some multi tenant applications and understand how they handle it. + +## Slack + + + + +### Authentication: + +* Slack implements a login screen that asks for email. +* The email is then mapped to a Slack account: `https://{account}.slack.com`. +* Slack likely maintains a mapping table between email (or email domain) and the Slack subdomain. Notice that in the example above, when entering `matias@auth0.com`, the user is redirected to `https://auth0.slack.com`. +* Slack only supports email/password authentication. This approach of asking the user for an email first, is a good starting point for enterprise SSO. Each organization will have configured its identity provider (SAML, ADFS, etc.) and Slack will redirect there once the user entered their email (instead of asking the user for credentials). + +### Authorization: + +* In Slack a user can belong to multiple teams (or organizations).Each team/org can be considered a __tenant__. +* You can switch from one team to another through an option in Slack. +* Users can either have __full__ or __single-channel__ access within a team (organization). +* A user can have __full access__ on one team but __single-channel access__ on another one. + +### Modeling this in Auth0 + +This `User Profile` can perfectly capture the intent in the requirements described above: + +``` +{ + email: 'matias@auth0.com' + permissions: [ + "auth0": + { + role: "admin", + channels: ["*"] + }, + "another-company": + { + role: "single-channel", + channels: [ "some-channel" ] + } + ] +} +``` + +## Dropbox + + + + +### Authentication: + +* Dropbox asks for an e-mail. Based on the e-mail suffix, if there is an organization with a domain that matches that suffix, it will hide the password textbox, and will show __Single Sign On Enabled__ message on the login screen. +* When the user clicks on the __Continue__ he will be redirected to the configured identity provider. +* If there is no SSO configured for that domain, the user will just enter the password. + +### Authorization: + +* Once a user is authenticated, he gets access to __folders/files__ he owns, and folders that were shared with him. +* Users get access to their own personal account and to any organization they belong to. + +### Modeling this in Auth0 + +``` +{ + email: 'foo@bar.com' + permissions: [ + "personal": + { + role: "full", + }, + "company1": + { + role: "user", + }, + "company2": + { + role: "admin", + } + ] +} +``` + +Storing the folders you have access to as part of the user profile in this case, would be overkill. The user object can get really big if the user has lots of folders. A more manageable approach is to store information at the company level role. All other ACLs are handled at the app/db level. + +## Auth0 + + + + +### Authentication: + +* Auth0 has a single dashboard for all the tenants and it is accesible through . +* It supports Google, GitHub, Live and User/Passwords authentication. +* It also supports Enterprise connections (you can request this through ) +* Auth0 uses email domains for home realm discovery (see screen below). Making it very similar to the Dropbox experience. + +### Authorization: + +* A user can belong to multiple tenants and have different permissions on each tenant (__user foo__ can be an __admin on tenant bar__ and a __regular user__ of __tenant xyz__). +* It is implemented by assigning the `user_id` property to an "account-level" entity together with the permission if the user has access to everything OR to an "app-level" entity if the user has an app level permission. + +### Modeling this in Auth0 + +``` +{ + email: 'foo@bar.com' + permissions: [ + "company1": + { + role: "full", + clients: [ "*" ] + }, + "company2": + { + role: "app-owner", + clients: [ "iaeonoemaoiy2ie029je" ] + } + ] +} +``` + + +# Building a multi-tenant app? How to use Auth0? + +Here are typical requirements of for a modern, SaaS multi-tenant app: + +* Allow users to signup with a custom __username/password__. Especially during trial periods. +* Allow users to login with their existing __Google__, __LinkedIn__ & __Yahoo!__ credentials. +* Small businesses with employees directories based on __Office365__ or __Google Apps__ have a preference for login using these credentials. +* Bigger companies often have already one or many identity systems for employees. Many rely on __Active Directory__, or __LDAP__. Some have deployed systems for identity federation based on __ADFS__, __PingFederate__, __Okta__, __OneLogin__, __CA Siteminder__, or even custom __SAML-P providers__. + +The app is primarily a web app, built as a __Single Page App__ (SPA) using __AngularJS__ with a backend API built with __nodejs__. They also have a mobile app for __Android__ and __iOS__ with a subset of the web app functionality. + +## Should you use one database connection or multiple ones? + +A single database connection is often sufficient. Whether the user has access to a certain tenant or not could very well be handled with [metadata](https://docs.auth0.com/api#!#put--api-users--user_id--metadata), not with different database connections. +The only case where it would make sense to use different DB connections is if you need to isolate a set of users (e.g. staging vs. prod environment), but even then we would recommend creating different accounts in Auth0 for that case. The other case where you would need a separate database connection would be if "tenant-A" use the builtin Auth0 user store but "tenant-B" has a set of users somewhere and you want to authenticate those users. In that case you would create a [custom db connection](https://docs.auth0.com/mysql-connection-tutorial) only for that tenant and somewhere in your application you would have to store that association. + +## Should I use a single Auth0 account for all tenants? + +Typically yes, one account for all tenants would be ok and it would allow you to manage everything from the same place. The only case in which it would make sense to have a **new** Auth0 account per tenant, would be if you want to share access to the Dashbord to the tenant. Notice that if you do that you would have to leverage the (restricted) API to create a new account. You can use the normal API to add applications and connections. + +> If you need access to the __account__ API, please contact us: [support@auth0.com](mailto:support@auth0.com) + +## How do I store different roles for each tenant? + +As explained above, the way to handle roles per tenant would be using [metadata](https://docs.auth0.com/api#!#put--api-users--user_id--metadata). + +Metadata in Auth0's user profile is a generic way of associating information to __any__ authenticated user. __Permissions__, __Groups__, __Roles__ are all special cases of these attributes. + +Here is an example that would model a simplified a chat system like "Slack": + +``` +{ + email: "matias@auth0.com" + permissions: [ + "auth0": + { + role: "admin", + channels: ["*"] + }, + "another-company": + { + role: "single-channel", + channels: [ "some-channel" ] + } + ] +} +``` + +> This article is under development. Feedback very welcome. diff --git a/docs/salesforce-clientid.md b/docs/salesforce-clientid.md index bba500c5..667def0e 100644 --- a/docs/salesforce-clientid.md +++ b/docs/salesforce-clientid.md @@ -6,7 +6,7 @@ To configure a Salesforce OAuth2 connection you will need to register your Auth0 Log into [login.salesforce.com](https://login.salesforce.com/), and click on the __New__ button of __Connected Apps__ in __Build | Create | Apps__: -![](img/salesforce-register-1.png) +![](//cdn.auth0.com/docs/img/salesforce-register-1.png) --- @@ -14,7 +14,7 @@ Log into [login.salesforce.com](https://login.salesforce.com/), and click on the Complete the basic information about your app (Connected App Name, API Name and Contact Email), complete the callback URL and define Selected OAuth Scopes and click on __Save__: -![](img/salesforce-register-2.png) +![](//cdn.auth0.com/docs/img/salesforce-register-2.png) https://@@account.namespace@@/login/callback @@ -24,7 +24,7 @@ Complete the basic information about your app (Connected App Name, API Name and Once the application is registered, enter your new `Consumer Key` and `Consumer Secret` into the connection settings in Auth0. -![](img/salesforce-register-3.png) +![](//cdn.auth0.com/docs/img/salesforce-register-3.png) diff --git a/docs/salesforce-community.md b/docs/salesforce-community.md new file mode 100644 index 00000000..eeccebe5 --- /dev/null +++ b/docs/salesforce-community.md @@ -0,0 +1,21 @@ +# Salesforce Community Auth + +Authenticating users in a Salesforce community uses different endpoints that the regular Salesforce app. + +The authorization URL for a Community site will be: + + https://{name of your community}.force.com/{community path}/oauth2/authorize + +For example, if your community is names __contoso__ and it is for __customers__: + + https://contoso.force.com/customers/oauth2/authorize?response_type=token&client_id=your_app_id&redirect_uri=your_redirect_uri + +Notice that Auth0 will automatically pass all required OAuth2 parameters (e.g. `response_type`, `client_id`, etc) and concatenate other elements to the path (e.g. `oauth2/authorize`). All that is required is that you configure the __base__ community site URL: + + https://contoso.force.com/customers + +For full details see this [Salesforce article](http://www.salesforce.com/us/developer/docs/chatterapi/Content/quickstart_communities.htm). + +It is common to customize the login page for __Community__ sites. If you do so, remember that the login page is part of the login transaction and you must honor the OAuth2 flow. + +[This sample](https://github.com/salesforceidentity/basic-custom-login) provides details on how to do it properly. diff --git a/docs/saml-configuration.md b/docs/saml-configuration.md index 9bfd5fe9..8e03e6ac 100644 --- a/docs/saml-configuration.md +++ b/docs/saml-configuration.md @@ -29,7 +29,7 @@ Below all the customizations you can do: * __createUpnClaim (`bool`):__ Whether or not a UPN claim should be created. Default is `true`. * __passthroughClaimsWithNoMapping (`bool`):__ If `true` (default), for each claim that is not mapped to the [common profile](user-profile), Auth0 will passthrough those in the output assertion. If `false`, those claims won't be mapped. Default is `true`. * __mapUnknownClaimsAsIs (`bool`):__ if `passthroughClaimsWithNoMapping` is `true` and this is `false` (default), for each claim that is not mapped to the [common profile](user-profile) Auth0 will add a prefix `http://schema.auth0.com`. If `true` it will passthrough the claim as-is. Default is `false`. -* __mapIdentities:__ If `true`, it will will add more information in the token like the provider used (google, adfs, ad, etc.) and the `access_token` if available. Default is `false`. +* __mapIdentities:__ If `true`, it will will add more information in the token like the provider used (google, adfs, ad, etc.) and the `access_token` if available. Default is `true`. * __signatureAlgorithm:__ Signature algorithm to sign the SAML Assertion or response. Default is `rsa-sha1` and it could be `rsa-sha256`. * __digestAlgorithm:__ Digest algorithm to calculate digest of the SAML Assertion or response. default `sha1`. It could be `sha256`. * __destination:__ Destination of the SAML Response. If not specified, it will be `AssertionConsumerUrl` of `SAMLRequest` or Callback URL if there was no SAMLRequest. diff --git a/docs/saml2webapp-tutorial.md b/docs/saml2webapp-tutorial.md new file mode 100644 index 00000000..6ce1650b --- /dev/null +++ b/docs/saml2webapp-tutorial.md @@ -0,0 +1,3 @@ +# Using Auth0 in SAML2 Web App + +Please enable and configure `SAML2 Web App` from Application Add-ons section and follow the instructions displayed. \ No newline at end of file diff --git a/docs/samlp.md b/docs/samlp.md index dc89c2af..6113bd6e 100644 --- a/docs/samlp.md +++ b/docs/samlp.md @@ -4,11 +4,16 @@ layout: doc.nosidebar --- # SAML Identity Provider Configuration -These are the paramters to configure on the SAML Identity Provider: - -* The post-back URL (also called Assertion Consumer Service URL) is: **https://@@account.namespace@@/login/callback** -* The Entity ID of the Service Provider is: **urn:auth0:@@account.tenant@@** -* Binding for SAML Request (sent to IdP): **HTTP-Redirect** -* Binding for the SAML Response (received from IdP): **HTTP-Post** -* NameID format: **unspecified** -* The SAML assertion, the SAML response, or both can be signed. +These are the parameters used to configure a SAML Identity Provider: + +* The __post-back URL__ (also called __Assertion Consumer Service URL__) is: **https://@@account.namespace@@/login/callback** +* The __Entity ID__ of the Service Provider is: **urn:auth0:@@account.tenant@@:@@connectionName@@** +* The __SAML Request Binding__ (sent to the IdP from Auth0): **HTTP-Redirect** +* The __SAML Response Binding__ (how the SAML token is received by Auth0 from IdP): **HTTP-Post** +* The __NameID format__: **unspecified** +* The SAML assertion, and the SAML response can be individually or simultaneously signed. +* Optional: Assertions can be encrypted. Use this public key to configure the IdP: [CER](https://@@account.namespace@@/cer) | [PEM](https://@@account.namespace@@/pem) | [PKCS#7](https://@@account.namespace@@/pb7) + +> If you want **IdP-Initiated SSO**, please make sure to include the connection parameter in the post-back URL: `https://@@account.namespace@@/login/callback?connection=@@connectionName@@` + +Some SAML Identity Providers can accept importing metadata directly with all the required information. You can access the metadata for your connection in Auth0 here: `https://@@account.namespace@@/samlp/metadata?connection=@@connectionName@@`. diff --git a/docs/samlsso-auth0-to-auth0.md b/docs/samlsso-auth0-to-auth0.md new file mode 100644 index 00000000..163982b4 --- /dev/null +++ b/docs/samlsso-auth0-to-auth0.md @@ -0,0 +1,331 @@ +# SAML SSO with Auth0 as ServiceProvider and as an Identity Provider + +This tutorial will create a simple example application that uses Auth0 to do SAML Single Sign On (SSO), using one Auth0 account (account 1) as a SAML Service Provider(SP), authenticating users against a second Auth0 account (account 2) serving as SAML Identity Provider(IDP). This gives you a way to test your Auth0 SAML account (account 1) configuration, using Auth0 as an IDP so you don't have to learn and set up another IDP. + +There are **9 sections** to this sample and a troubleshooting section at the end. + +1. Establish two Auth0 accounts. +2. Set up the Auth0 Identity Provider (IDP) (account 2). +3. Set up the Auth0 Service Provider (SP) (account 1). +4. Add your Service Provider metadata to the Identity Provider. +5. Test Identity Provider. +6. Test the connection from Service Provider to Identity Provider. +7. Register a simple HTML application with which to test the end-to-end connection. +8. Create the HTML page for a test application. +9. Test your creation. + +##1. Establish two Auth0 accounts + +If you do not already have two Auth0 accounts, you will need to create them. + +###In the Auth0 dashboard: + +In the upper right corner, click on the name of your account and in the popup menu which appears, select **"New Account"**. + +In the window which appears, enter a name for your second account in the **"Your Auth0 domain"** field and press the **"SAVE"** button. + +You can switch back and forth between the accounts by going to the upper right corner of the dashboard, clicking on the name of the current account, and using the popup menu which appears to switch between your accounts. + +![](https://cdn.auth0.com/docs/img/saml-auth0-1.png) + +##2. Set up the Auth0 IDP (account 2) + +In this section you will configure one Auth0 account (account 2) to serve as an Identity Provider. You will do this by registering an application, but in this case, the 'application' you register is really a representation of account 1, the SAML Service Provider. + +Log into **Account 2** + +**In the Auth0 dashboard:** + +1. Click on **"Apps/APIs"** link at left. + +2. Click on the red **"+ NEW APP/API"** button on the right. + +3. In the **Name** field, enter a name like "My-Auth0-IDP". + +4. Press the blue **"SAVE"** button. + +5. In the **Auth0 dashboard**, click again on the **"Apps/APIs"** link at left + +6. Find the row for the application you just created, and click on the **"Settings"** icon to the right of the application name. (the round gear icon) + +7. Scroll down and click on the **"Advanced Settings"** link. + +8. In the expanded window, scroll down to the **"CERTIFICATES"** section and click on the **"DOWNLOAD CERTIFICATES"** button. In the popup which appears, select "PEM" to select a PEM-formatted certificate. The certificate will be downloaded to a file called "@@account.tenant@@.pem". Save this file as you will need to upload this file when configuring the other Auth0 account, account 1. + +9. Scroll down further to the **"ENDPOINTS"** section and click on the blue **"SAML"** tab within that section. Copy the entire contents of the **"SAML Protocol URL"** field and save it as in the next step you will need to paste it into the other Auth0 account, account 1. + +![](https://cdn.auth0.com/docs/img/saml-auth0-2.png) + +Next, create a user to use in testing the SAML SSO sequence. +**In the Auth0 dashboard:** + +1. Click on **"Users"** link at left. + +2. Click on the red **"+ NEW USER"** button on the right. + +3. In the **Email** field, enter an email for your test user. The domain name for the email should match what you enter in section 3 below. For example, if your user is `john.doe@abc-example.com`, you would enter that here, and then enter "abc-example.com" in step 3 below for the Email domain. + +4. Enter a password for the user + +5. For the Connection, leave it at the default value. (Username-Password-Authentication) + +6. Press the blue **"SAVE"** button. + + +##3. Set up the Auth0 service provider (account 1) + +In this section you will configure another Auth0 account (account 1) so it knows how to communicate with the second Auth0 account (account 2) for single sign on via the SAML protocol. + +Log out of **Account 2** and log into **Account 1**. + +**In the Auth0 dashboard:** + +1. Click on **"Connections"** link at left. +2. In the list of options below "Connections", click on **"Enterprise"** +3. In the middle of the screen, click on **"SAMLP Identity Provider"** +4. Click on the blue **"Create New Connection"** button + + +In the **"Create SAMLP Identity Provider"** connection window, enter the following information into the "Configuration" tab. + +**Connection Name:** You can enter any name, such as "SAML-Auth0-IDP" + +**Email Domains:** In this example, we will use the Lock Widget, so in the Email Domains field enter the email domain name for the users that will log in via this connection. +For example, if your users have an email domain of 'abc-example.com', you would enter that into this field. You can enter multiple email domains if needed. Make sure the test user you created in section 2 has an email address with email domain that matches what you enter here. + +**Sign In URL:** enter the **"SAML Protocol URL"** field that you copied in section 2 above. (From account 2 dashboard, Apps/APIs link, Settings tab, Advanced Settings, ENDPOINTS section, SAML tab, "SAML Protocol URL" field.) + +**Sign Out URL:** enter the same URL as for the Sign In URL above. + +**Certificate:** +Click on the red **"UPLOAD CERTIFICATE"** button and select the `.pem` file you downloaded from account 2 in section 2 above. + +You can ignore the rest of the fields for now. + +**Save:** Click on the blue **"SAVE"** button at the bottom. + +Here is an example of what the filled-out screen would look like: (you should have filled out the Email domains field as well with your specific test user's email domain.) + +![](https://cdn.auth0.com/docs/img/saml-auth0-3.png) + + +After pressing the **"SAVE"** button, A window will appear with a red **"CONTINUE"** button. (You might have to scroll up to see it) + +Click on the **"CONTINUE"** button. + +In the window that appears, metadata about this SAML provider (account 1) is displayed. You will need to collect two pieces of information about this Auth0 account (the service provider) that you will then paste into the other Auth0 account you set up (the identity provider). + +First, look for the second bullet in the list of information that tells you the **"Entity ID"**. It will be of the form __urn:auth0:@@account.tenant@@:@@connectionName@@__. + +Copy and save this entire Entity ID field from "urn" all the way to the end of the connection name. + +In that same window, near the bottom, there is a line that says, _"You can access the metadata for your connection in Auth0 here:"_. + +Copy the URL below that line into your browser address bar. The picture below shows the screen on which this URL will appear and where to find it: + +![](https://cdn.auth0.com/docs/img/saml-auth0-4.png) + +In general, you can access the metadata for a SAML connection in Auth0 here: `https://@@account.namespace@@/samlp/metadata?connection=@@connectionName@@`. + +Once you go to that metadata URL, it will display the metadata for the Auth0 account 1 (service provider side of the federation. It will look something like the following with your account name in place of the 'xxxxx': + +![](https://cdn.auth0.com/docs/img/saml-auth0-5.png) + +You need to locate the row that starts with **"AssertionConsumerService"** and copy the value of the **"Location"** field. It will be a URL of the form __https://@@account.tenant@@.auth0.com/login/callback?connection=@@connectionName@@__. + +Copy and save this URL. This is the URL on account 1 that will receive the SAML assertion from the IDP. In the next section you will give this URL to the IDP so it knows where to send the SAML assertion. + + +# 4. Add your Service Provider metadata to the Identity Provider + +In this section you will go back and add some information about the Service Provider (account 1) to the Identity Provider (account 2) so the Identity Provider Auth0 account knows how to receive and respond to SAML-based authentication requests from the Service Provider Auth0 account. + +* Log out of **Account 1** and log back into **Account 2**. + +**In the Auth0 dashboard:** for Account 2 + +1. Click on **"Apps/APIs"** link at left. + +2. Find the row for the application you created earlier, and click on the **"Add Ons"** icon to the right of the application name. (the angle bracket and slash icon) +3. Locate the box with the **"SAML2 WEB APP"** label and click on the circle toggle to turn it green. + + +![](https://cdn.auth0.com/docs/img/saml-auth0-6.png) + +4. Next, click on the **"SAML2 WEB APP"** box itself to access the **"Addon: SAML2 Web App"** popup. Make sure you are in the **Settings**" tab. + +5. In the **"Application Callback URL"** field, paste in the **Assertion Consumer Service URL** that you copied and saved in section 3 above (the last step). + +6. In the Settings field below, go to line 2 that has the "audience" attribute. + +First, replace all single quote marks in that line with double quote marks. + +Then remove the "//" at the beginning of the line to uncomment it. + +Next, replace the original value (urn:foo) with the **Entity ID** value you saved and copied in step 3 above. The new line 2 should look something like: + +```` + "audience":"urn:auth0:@@account.tenant@@:@@connectionName@@" +```` + +7. Click on the blue **"SAVE"** button at the bottom of the screen + +![](https://cdn.auth0.com/docs/img/saml-auth0-7.png) + + +# 5. Test Identity Provider + +In the same screen, click on the red **"DEBUG"** button. + +That will trigger a login screen from account 2, the Identity Provider. + +Log in with the credentials for account 2. + +If your configuration is correct, you will see a screen titled **"It works!"** + +This screen will show you the encoded and decoded SAML response that would be sent by the Identity Provider. + +Check the decoded SAML response and locate (about half-way down) the **""** tag and make sure it matches the **Entity ID** you entered in the previous screen (obtained during step 3). + +Click on **"Close this window"** at the bottom of the screen. + +![](https://cdn.auth0.com/docs/img/saml-auth0-8.png) + +# 6. Test the connection from Service Provider to Identity Provider + +In this section, you will test to make sure the SAML configuration between Auth0 account 1 (Service Provider) and Auth0 account 2 (Identity Provider) is working. + +Log out of Account 2 and return to Account 1. + +* In the **Auth0 dashboard**, navigate to: __Connections -> Enterprise -> SAMLP Identity Provider__. + +* Click on the triangular **"Try"** button for the SAML connection you created earlier. This button is to the right of the name of the connection. You can hover your mouse over the button to have the text label appear. + +* You will first see a Lock login widget appear that is triggered by the Service Provider. Enter the username of the test account you created earlier. + +You will then be redirected to the Lock login widget of the Identity Provider. Login with the credentials for the test user you created. + +If the SAML configuration works, your browser will be redirected back to an Auth0 page that says __"It works!!!"__. This page will display the contents of the SAML authentication assertion sent by the Auth0 Identity Provider to Auth0 Service Provider. +This means the SAML connection from Auth0 Service Provider to Auth0 Identity Provider is working. + +If it didn't work, double check the above steps and then consult the **troubleshooting** section at the end of this document. + +> NOTE: the **Try** button only works for users logged in to the Auth0 dashboard. You cannot send this to an anonymous user to have them try it. + +Here is a sample of the **"It Works"** screen: + +![](https://cdn.auth0.com/docs/img/saml-auth0-9.png) + +# 7. Register a simple HTML application with which to test the end-to-end connection. + +In this section, you will register an application in Auth0 that will use the SAML connection you set up in the above steps. + +Make sure you are logged into the **Account 1 Auth0 dashboard**. + +* In the **Auth0 dashboard**, click on the **"Apps/APIs"** link at left. + +* Click on the red **"+ NEW APP/API"** button on the right. + +* In the **Name** field, enter a name like "My-HTML-SAML-App". + +* Press the blue **"SAVE"** button. + +* In the **Auth0 dashboard**, click again on the **"Apps/APIs"** link at left + +* Find the row for the application you just created, and click on the **"Settings"** icon to the right of the application name. (the round gear icon) + +* In the **"Allowed Callback URL"** field, enter **[http://jwt.io](http://jwt.io)**. +* The list of allowed callback URLs is a list of URL(s) to which users will be redirected after authentication. The URL(s) entered here must match the **"callback URL"** in the HTML code created in the next step. Normally you would enter a URL for your application, but to keep this example simple, users will simply be sent to the Auth0 JWT online tool which will provide some information about the JASON Web Token returned at the end of the authentication sequence. + +* Press the blue **"SAVE CHANGES"** button at the bottom of the screen. + +* In the same screen, click on the blue **"Connections"** tab (In the row that says Quick Start, Settings etc. + +* Scroll down to the section near the bottom where it says **"ENTERPRISE"**. + +* Find the row for the SAML connection you created above and click on the on/off toggle at right so that it is green, for "on". That enables the SAML connection for this application. + +![](https://cdn.auth0.com/docs/img/saml-auth0-12.png) + +# 8. Create the HTML page for a test application + +In this section you will create a very simple HTML page that invokes the **Auth0 Lock Widget** which will trigger the SAML login sequence. This will enable an end-to-end test of the SAML SSO. + +Create an HTML page and insert the following HTML and javascript code: + + +``` + + + +

      Click on the button to log in

      + + + + + + + +``` + +Make sure you replace `{YOUR-APP-CLIENT-ID}` with the actual value of the app you registered in step 7 above. + +The client ID for your application can be found in the **Auth0 dashboard** for **Account 1** by going to __"Apps/APIs"__ link and clicking on the __"Settings"__ (gear) icon to the right of your application name. + +Save this file in a place where you can access it via a browser. +For this example, we'll call it **"hello-saml.html"**. + +# 9. Test your sample application + +In this step, you will test your sample HTML application that uses the Auth0 SAML connection you set up in Account 1 to perform SSO via SAML against Account 2, serving as the SAML Identity Provider. + +* Open the HTML file created above with a browser. You should first see a white page with a login button on it. + +* Click on the **login** button. + +The **Auth0 Lock** widget should appear with one button titled **"saml"**. + +If you have other connections turned on for your application, your **Auth0 Lock Widget** may look slightly different. If you are prompted to enter an email address, make sure the email address you enter has the same domain name as the domain(s) you entered in the __Settings__ tab for the application in the Account 1 Auth0 dashboard. (__Apps/APIs -> Settings__) + +After entering your email address, the blue button on the Lock widget may have a new label. Click on the button which may be labeled **"saml"** or **ACCESS** or with the email domain of the email address you are logging in with, to initiate the SAML sso sequence with the Auth0 Identity Provider. + +![](https://cdn.auth0.com/docs/img/saml-auth0-10.png) + +* You will be redirected to the Identity Provider to log in. + +Note that whether you are prompted for credentials at this point depends on whether you still have an active session at the Identity Provider. + +From the "try me" test you did earlier, you may still have an active session at the Identity Provider. If this is the case, you will not be prompted to log in again and will simply be redirected to the callback URL specifed in the HTML file. (Remember that this callback URL must also be in the __Allowed Callback URLs__ in the application's Setting tab in the Auth0 dashboard.) + +If sufficient time has passed, or if you delete your browser cookies before initiating the test, then you will be prompted to login when redirected to the Identity Provider. Log in to the Identity Provider using the credentials for the test user you created in Auth0 Account 2. + +![](https://cdn.auth0.com/docs/img/saml-auth0-11.png) + +Upon successful authentication, you will be redirected to the callback URL specified in the HTML file (jwt.io). + +#10. Troubleshooting. + +This section has a few ideas for things to check if your sample doesn't work. + +Note that if your application doesn't work the first time, you should clear your browser history and ideally cookies each time before you test again. Otherwise, the browser may not be picking up the latest version of your html page or it may have stale cookies that impact execution. + +When troubleshooting SSO, it is often helpful to capture an HTTP trace of the interaction. There are many tools that will capture the HTTP traffic from your browser for analysis. Search for "HTTP Trace" to find some. Once you have an http trace tool, capture the login sequence from start to finish and analyze the trace to see the sequence of GETs to see how far in the expected sequence you get. You should see a redirect from your original site to the Service Provider, and then to the Identity Provider, a post of credentials if you had to log in, and then a redirect back to the callback URL or the Service Provider and then finally a redirect to the callback URL specified in your application. + +Be sure to check to make sure cookies and javascript are enabled for your browser. + +Check to make sure that the callback URL specified in the HTML file is also listed in the **Allowed Callback URLs** field in the __""Settings""__ tab of the application registered in the Auth0 Dashboard. (In dashboard, Click on __"Apps/APIs"__ link, then on the __"Settings"__ icon to the right of the application name.) + +The **[http://samltool.io](http://samltool.io)** tool can decode a SAML assertion and is a useful debugging tool. + diff --git a/docs/scenarios/amazon-cognito.md b/docs/scenarios/amazon-cognito.md new file mode 100644 index 00000000..011600d9 --- /dev/null +++ b/docs/scenarios/amazon-cognito.md @@ -0,0 +1,114 @@ +# Integrating Auth0 with Amazon Cognito for Mobile Apps. + +**Amazon Cognito** is a new backend as a service that lets you focus on writing a fantastic user experience for your client app (native or web). + +In this document, I’ll explain how you can integrate your mobile app with two solutions: Auth0 to get authentication with either [Social Providers](https://auth0.com/docs/identityproviders#2) (Facebook, Twitter, etc.), [Enterprise providers](https://auth0.com/docs/identityproviders#1) or regular Username and Password, and [Amazon Cognito](http://aws.amazon.com/cognito/), to get a backend for your app without writing a line of code + +## Configuring Amazon Web Services +### Create a new OpenID Connect Provider +The first step is to create an OpenID Connect Provider pointing to your Auth0 account. Please take a note of your auth0 domain (_accountName_.auth0.com) and your _clientId_ and use them to create the Identity Pool in the [IAM Console](https://console.aws.amazon.com/iam/home) + +![IDP Creation](https://cdn.auth0.com/blog/IDPCreation.gif) + + +### Create a Cognito Identity Pool +Now, you need to create an Identity Pool in [Cognito Console](https://console.aws.amazon.com/cognito/home). This will be used to login to Amazon Cognito using the Auth0’s Identity Provider that you created in the previous step. + +![Cognito Pool Creation](https://cdn.auth0.com/blog/IDPCognito.gif) + +> Please take a note of the ARN selected in the end of the gif. We’ll use it later on! + +### Grab the Role ARN +Finally, grab the ARN of the role that was automatically created in the previous step from the [IAM console](https://console.aws.amazon.com/iam/home). + +![Grab Role ARN](https://cdn.auth0.com/blog/Roles.gif) + +# Configuring Auth0 +### Configure your application +Amazon will use the public signing key from the OpenID Provider Metadata (https://subscription.auth0.com/.well-known/jwks.json) to validate the signature of the Json Web Token. + +By default Auth0 will use the HS256 signature algorithm which is not supported in this scenario (this will result in "Invalid login token" errors). Go to your application in the dashboard, press "Show Advanced Settings" and change the algorithm to RS256: + +![](https://cdn.auth0.com/docs/img/cdn-amazon-cognito-rs256.png) + +## Code time! +Now it’s time to start coding our app. In this case, we’ll be using Swift, but the same sample applies to Objective C as well. + +### Adding the Needed Dependencies + +Add the following dependencies to your `Podfile` + +````ruby +pod "Lock", "~> 1.7", :inhibit_warnings => true +pod "JWTDecode", "~> 0.2" +pod "SimpleKeychain", "~> 0.2" +pod 'AWSCognitoSync', "~> 1.0" +```` +### Logging the User In +We’ll use [Auth0 Lock for iOS](https://github.com/auth0/lock) to log the user in. You can read detailed instructions on how to implement it in [this documentation page](https://auth0.com/docs/native-platforms/ios-swift). +Once the user is successfully logged in with Auth0, we’ll send his or her credentials to Amazon Cognito: + +````swift +let authController = A0LockViewController() +authController.onAuthenticationBlock = {(profile:A0UserProfile!, token:A0Token!) -> () in + // Save Tokens and Credentials into the keychain as you'd regularly do + // ... + + let provider = AWSCognitoCredentialsProvider.credentialsWithRegionType( + AWSRegionType.USEast1, + // Your AWS Account ID + accountId: Constants.AWSAccountID.value, + // This is the ARN from Cognito Identity Pool + identityPoolId: Constants.CognitoPoolID.value, + unauthRoleArn: '', + // This is the ARN from the Role + authRoleArn: Constants.CognitoRoleAuth.value); + + + let configuration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: self.provider); + AWSServiceManager.defaultServiceManager().setDefaultServiceConfiguration(configuration) + + // Set here the Auth0 URL you've set when creating the OpenID Connect Provider + provider.logins = ['samples.auth0.com': token.idToken] + self.provider.getIdentityId().continueWithBlock { (task: BFTask!) -> AnyObject! in + self.provider.refresh() + if (task.error != nil) { + // Fail + } else { + // Logged in with Cognito successfully + } + return nil + } +} +```` + +### Using Cognito + +Now, the user is logged in to Cognito through Auth0. You can now store information in Cognito that only this user will be able to use. + +````swift +let cognitoSync = AWSCognito.defaultCognito() +let dataset = cognitoSync.openOrCreateDataset("MainDataset") +// Get an existing value +dataset.synchronize().continueWithBlock { (task) -> AnyObject! in + dispatch_async(dispatch_get_main_queue(), { () -> Void in + self.textValue.text = dataset.stringForKey("value") + }) + return nil +} + +// Set a new value +dataset.setString(self.textValue.text, forKey: "value") +dataset.synchronize() +```` +## Let’s see how it works! + +![Cognito working](https://cdn.auth0.com/blog/CognitoSample.gif) + +## Final Thoughts + +That’s it! Now, your users can login with Github or any other identity provider using a native UI and save their information with Cognito easily. + +You can check out a working sample that uses Cognito and Auth0 in [this Github repository](https://github.com/auth0/Lock.iOS-OSX/tree/master/Examples/Cognito.Swift) + +Happy coding :). diff --git a/docs/scenarios/index.md b/docs/scenarios/index.md new file mode 100644 index 00000000..85f8a3b9 --- /dev/null +++ b/docs/scenarios/index.md @@ -0,0 +1,51 @@ +--- + url: /scenarios +--- + +# Common SSO Scenarios + +* [SaaS apps](saas-apps) +* [SSO](sso/single-sign-on) +* [Home Realm Discovery](hrd) + +- - - + +# Advanced Scenarios + +* [Invite-only users](invite-only) + +- - - + +# API integrations + +* [AWS](aws) +* [Ionic and Firebase](scenarios/ionic-and-firebase) +* [Amazon Cognito](scenarios/amazon-cognito) +* SAP Netweaver Gateway +* Windows Azure Mobile Services +* Windows Azure Service Bus + +- - - + +# Integration scenarios + +* [Keen.io](scenarios/keenio) +* [Mixpanel Fullcontact Salesforce](scenarios/mixpanel-fullcontact-salesforce) +* [Parse](scenarios/parse) +* [Rapleaf Salesforce](scenarios/rapleaf-salesforce) +* [Segment.io](scenarios/segmentio) +* [Splunk](scenarios/splunk) +* [Unbounce](scenarios/unbounce) +* [Zendesk](scenarios/zendesk-sso) + +- - - + +# Internet of Things + +* [MQTT](scenarios/mqtt) +* [Tessel](scenarios/tessel) + +- - - + +> Using Auth0 in other ways? [Let us know!](mailto://support@auth0.com?Subject=I'm building something really cool) + diff --git a/docs/scenarios/ionic-and-firebase.md b/docs/scenarios/ionic-and-firebase.md new file mode 100644 index 00000000..33eaa76a --- /dev/null +++ b/docs/scenarios/ionic-and-firebase.md @@ -0,0 +1,301 @@ +--- +lodash: true +--- + +## Ionic Framework + Firebase Tutorial + +You can either download the sample project, or follow the instructions below. + + + +### 1. Setting up the callback URL in Auth0 + +
      +

      Go to the Application Settings section in the Auth0 dashboard and make sure that Allowed Callback URLs contains the following values:

      + +
      https://@@account.namespace@@/mobile, file://*, http://localhost:8100
      +
      + +
      + +### 2. Configure Auth0 to work with Firebase + +You need to add your Firebase account information to Auth0. Once the user logs in to the App, Auth0 will use this information to issue a Firebase authentication token. + +Go to [Application Settings](@@uiAppSettingsURL@@) and click on Addons. In there, turn on the __Firebase Addon__ and enter your Firebase secret. + +![Firebase secret](http://d.pr/i/1ejf7+) + + +### 3. Adding the needed dependencies + +Add the following dependencies to the `bower.json` and run `bower install`: + +````json +"dependencies" : { + "auth0-angular": "4.*", + "a0-angular-storage": ">= 0.0.6", + "angular-jwt": ">= 0.0.4", + "angularfire": "~0.9.2" +}, +``` + +### 4. Add the references to the scripts in the `index.html` + +````html + + + + + + + + + + + +``` + +### 5. Add `InAppBrowser` plugin + +You must install the `InAppBrowser` plugin from Cordova to be able to show the Login popup. Run the following command: + +````bash +ionic plugin add org.apache.cordova.inappbrowser +``` + +Add the following configuration to the `config.xml` file: + +````xml + + + + +``` + +### 6. Add the module dependency and configure the service + +Add the `auth0`, `angular-storage`, `angular-jwt` and `firebase` module dependencies to your angular app definition and configure `auth0` by calling the `init` method of the `authProvider` + +````js +// app.js +angular.module('starter', ['ionic', + 'starter.controllers', + 'starter.services', + 'auth0', + 'angular-storage', + 'angular-jwt', + 'firebase']) +.config(function($stateProvider, $urlRouterProvider, authProvider, $httpProvider, + jwtInterceptorProvider) { + + + $stateProvider + // This is the state where you'll show the login + .state('login', { + url: '/login', + templateUrl: 'templates/login.html', + controller: 'LoginCtrl', + }) + // Your app states + .state('dashboard', { + url: '/dashboard', + templateUrl: 'templates/dashboard.html', + data: { + // This tells Auth0 that this state requires the user to be logged in. + // If the user isn't logged in and he tries to access this state + // he'll be redirected to the login page + requiresLogin: true + } + }) + + ... + + authProvider.init({ + domain: '<%= account.namespace %>', + clientID: '<%= account.clientId %>', + loginState: 'login' + }); + + ... + +}) +.run(function(auth) { + // This hooks all auth events to check everything as soon as the app starts + auth.hookEvents(); +}); +``` + + +### 7. Implementing login + +Now you're ready to implement the Login. You can inject the `auth` service in any controller and just call `signin` method to show the Login / SignUp popup. +In this case, add the call in the `login` method of the `LoginCtrl` controller. +After a successful login, you will: + +1. Save the __user profile__. +2. Save the __token__ and __[refresh token](@@base_url@@/refresh-token)__ +3. Call Auth0 to issue a __Firebase token__ and save it as well. + +> All these artifacts are persisted into `localStorage` in the browser. The Firebase token is obtained through Auth0's [Delegation endpoint](https://auth0.com/docs/auth-api#delegated). + +````js +// LoginCtrl.js +function LoginCtrl(store, $scope, $location, auth) { + $scope.login = function() { + auth.signin({ + authParams: { + scope: 'openid offline_access', + device: 'Mobile device' + } + }, function(profile, token, accessToken, state, refreshToken) { + store.set('profile', profile); + store.set('token', idToken); + store.set('refreshToken', refreshToken); + auth.getToken({ + api: 'firebase' + }).then(function(delegation) { + store.set('firebaseToken', delegation.id_token); + $state.go('dashboard'); + }, function(error) { + // Error getting the firebase token + }) + }, function() { + // Error callback + }); + } +} +``` + +````html + + + + +``` + +> Note: there are multiple ways of implementing login. The example above uses the __Auth0 Lock__ a component that implements a ready to use login UI. You can build your own UI changing the ` + +``` + +2. Add a button (or whatever UI element you consider) that will trigger the login with the provider. Take note of the button ID under Advanced. + +3. You need a way to pass the information coming from the social providers to Unbounce. The way you do that is by creating a Form and add Hidden fields for each field. In the following example we are using the [normalized profile](user-profile) fields `name`, `email`, `given_name`, `family_name`, `nickname` and `picture` and at the end you can see a LinkedIn field called `headline`. + + ![](https://cloudup.com/caDtUPj4EO3+) + +3. Finally, go back to the JavaScript editor at Unbounce and add a click handler for each button to trigger the social authentication. + +``` +$('#REPLACE_WITH_BUTTON_ID').bind('click', function() { + auth0.login({ + connection: 'REPLACE_WITH_CONNECTION_NAME', // you get the connection name from Auth0 dashboard (expand social provider) + popup: true + }, callback); + + return false; +}); + +function callback(err, profile, id_token, access_token, state) { + if (err) alert('There was an error, please try again'); + + // normalized attributes from Auth0 + $('#INPUT_1').val(profile.name); + $('#INPUT_2').val(profile.email); + $('#INPUT_3').val(profile.given_name); + $('#INPUT_4').val(profile.family_name); + $('#INPUT_5').val(profile.nickname); + $('#INPUT_6').val(profile.picture); + + // provider-speicifc attributes + if (profile.headline) $('#INPUT_7').val(profile.headline); +} + +``` + +> Note: the name of the connection (REPLACE_WITH_CONNECTION_NAME) can be taken from the Auth0 dashboard under Connections -> Social and expanding the provider. Also, make sure to change the input IDs. + diff --git a/docs/scenarios/zendesk-sso.md b/docs/scenarios/zendesk-sso.md new file mode 100644 index 00000000..0521b43f --- /dev/null +++ b/docs/scenarios/zendesk-sso.md @@ -0,0 +1,104 @@ +# Single Sign-On with Zendesk using JWT + +This [Zendesk article](https://support.zendesk.com/hc/en-us/articles/203663816-Setting-up-single-sign-on-with-JWT-JSON-Web-Token-) details everything you need to know about getting SSO to work with any JWT provider. + +Using Auth0 will allow you to quickly set up Zendesk logins with **LDAP**, **Active Directory**, **Google Apps** or any of the [supported identity providers](https://docs.auth0.com/scenarios/identityproviders). + +> **Note:** If you happen to lock yourself out of your Zendesk account while following this tutorial, you can always log in with your regular Zendesk credentials at https://your_domain.zendesk.com/access/normal + +You can enable SSO for administrators/agents, users, or both. In any case, the configuration is the same. + +## 1. Enable SSO with JWT + +Log in to your Zendesk dashboard, go into your **Security** settings and enable **SSO** with **JSON Web Token**: + +![](//cdn.auth0.com/docs/img/zendesk-sso-1.png) + +Here you'll need to configure the **Remote login URL**, which points to a webpage where your users will be able to sign in, for example, using [Lock](/lock). +Also, take note of the **Shared secret** here, we'll be using it in the next step. + +## 2. Implement a rule to create a JWT for Zendesk + +We want to create a JWT with a user's information after login that Zendesk can understand. +We can achieve this using a [rule](/rules). +In this example, we're storing the Zendesk **Shared secret** as an encrypted key-value pair, which can be accessed with `configuration.ZENDESK_JWT_SECRET`. + +```js +function (user, context, callback) { + + // Assuming your Zendesk URL is https://foo.zendesk.com + var ZENDESK_SUBDOMAIN = 'foo'; + + // Generate a random UUID: http://en.wikipedia.org/wiki/Universally_unique_identifier + // Used in the token's jti claim to avoid replay attacks + function uuid() { + var s = []; + var hexDigits = "0123456789abcdef"; + for (var i = 0; i < 36; i++) { + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); + } + s[14] = "4"; + s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); + s[8] = s[13] = s[18] = s[23] = "-"; + + var id = s.join(""); + return id; + } + + // Create the JWT as required by Zendesk + var payload = { + iat: new Date().getTime() / 1000, + jti: uuid(), + email: user.email, + name: user.name, + external_id: user.user_id + }; + + // Sign the token and add it to the profile + var zendesk_token = jwt.sign(payload, configuration.ZENDESK_JWT_SECRET); + user.zendesk_jwt_url = 'https://' + ZENDESK_SUBDOMAIN + '.zendesk.com/access/jwt?jwt=' + zendesk_token; + + callback(null, user, context); +} +``` + +## 3. Redirect users to the URL returned by Zendesk + +The rule returned in the previous step will return a user property named `zendesk_jwt_url`, which will allow your user to log in. +If you are using [Lock](lock), for example: + +```js +lock.show(function (err, profile, id_token) { + if (err) { + console.error('Something went wrong!', err); + } else if (profile.zendesk_jwt_url) { + document.getElementById('zendesk_url').href = profile.zendesk_jwt_url; + } +}); +``` + +```html +Click here to SSO with Zendesk +``` + +That's it! +Your users can now use any of Auth0's supported identity providers to single sign-on with Zendesk. + +### Optional: Redirecting users to the URL they originally visited + +When Zendesk redirects users to your login page, a [`return_to` query parameter](https://support.zendesk.com/hc/en-us/articles/203663816-Setting-up-single-sign-on-with-JWT-JSON-Web-Token-#topic_hkm_kst_kk) is added to your login page URL. +This parameter contains the Zendesk URL where your users should be redirected after login. +It should be added to the Zendesk URL returned by the above rule: + +```js +// Using URI.js, see http://medialize.github.io/URI.js/ +return_to = URI(document.URL).query(true)['return_to']; + +lock.show(function (err, profile, id_token) { + if (err) { + console.error('Something went wrong!', err); + } else if (profile.zendesk_jwt_url) { + document.getElementById('zendesk_url').href = URI(profile.zendesk_jwt_url).addSearch({return_to: return_to}); + } +}); +``` diff --git a/docs/search.md b/docs/search.md new file mode 100644 index 00000000..0c08add9 --- /dev/null +++ b/docs/search.md @@ -0,0 +1,11 @@ +--- +title: Search +--- + +## Search results + +Type for any term on the top right search box and submit so we can help you find what you need to get started. + + +
      + diff --git a/docs/sequence-diagrams.md b/docs/sequence-diagrams.md new file mode 100644 index 00000000..55346054 --- /dev/null +++ b/docs/sequence-diagrams.md @@ -0,0 +1,43 @@ +# Sequence Diagrams + +## Single Page Applications + +The following sequence diagram shows the typical authentication flow of a single page application. + + Replay + + + +1. Initiate the login transaction by redirecting the user to: + +
      GET https://@@account.namespace@@/authorize/?
      +        response_type=token
      +        &client_id=@@account.clientId@@
      +        &redirect_uri=YOUR_CALLBACK_URL
      +        &state=VALUE_THAT_SURVIVES_REDIRECTS
      +        &scope=openid
      + + > You can optionally use the [Login Widget](login-widget2) to trigger the login. Or you can do it manually and send a `connection` parameter in the querystring that will redirect the user straight to the desired connection. + +2. The user will authenticate on the identity provider typically hosted somewhere else. Auth0 speaks OAuth2, OAuth1, OpenID, SAML and Ws-Federation. + +3. After the user authenticates, your app will be called back to this endpoint with a `GET` + +
      GET YOUR_CALLBACK_URL#
      +        access_token=2YotnF..........1zCsicMWpAA
      +        &id_token=......Json Web Token......
      +        &state=VALUE_THAT_SURVIVES_REDIRECTS
      + +4. Call the Auth0 API to validate the token and get back the user profile + +
      GET https://@@account.namespace@@/api/users/:userid
      +        Authorization: Bearer ...id_token...
      + +5. Finally, you can call your API + +
      GET https://your.api/foo
      +        Authorization: Bearer ...id_token...
      + +6. Your API needs to validate the token as well with the client secret. See **APIs** section on the Sidebar. + +---- diff --git a/docs/server-apis/aspnet-webapi.md b/docs/server-apis/aspnet-webapi.md new file mode 100644 index 00000000..2796d968 --- /dev/null +++ b/docs/server-apis/aspnet-webapi.md @@ -0,0 +1,67 @@ +--- +lodash: true +--- + +## ASP.NET Web API Tutorial + + + +**Otherwise, please follow the steps below to configure your existing ASP.NET Web API app to use it with Auth0.** + +### 1. Install the WebApi.JsonWebToken package + +You can either run the following command or install it via **Package Manager**. +````Powershell +Install-Package WebApi.JsonWebToken +``` + +### 2. Configure the JsonWebToken message handler + +Open the **WebApiConfig.cs** file located in the **App_Start** folder and add the following `using` statements: +````CSharp +using Api.App_Start; +using System.Web.Configuration; +``` + +Add the following code snippet inside the `Register` method. +````CSharp +var clientID = WebConfigurationManager.AppSettings["Auth0ClientID"]; +var clientSecret = WebConfigurationManager.AppSettings["Auth0ClientSecret"]; + +config.MessageHandlers.Add(new JsonWebTokenValidationHandler() +{ + Audience = clientID, + SymmetricKey = clientSecret +}); +``` + +### 3. Update the web.config file with your app's credentials +Open the **web.config** file located at the solution's root. + +Add the following entries as children of the `` element. +````xml + + +``` + +### 4. Securing your API +All you need to do now is add the `[System.Web.Http.Authorize]` attribute to the controllers/actions for which you want to verify that users are authenticated. + +### 5. You're done! + +Now you have both your FrontEnd and Backend configured to use Auth0. Congrats, you're awesome! + + +### Optional Steps +#### Configuring CORS + +You can follow [this article](http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api) to configure CORS in your application. diff --git a/docs/server-apis/aws.md b/docs/server-apis/aws.md new file mode 100644 index 00000000..d9f654d8 --- /dev/null +++ b/docs/server-apis/aws.md @@ -0,0 +1,11 @@ +--- +title: AWS +lodash: true +--- +<% configuration.thirdParty = 'AWS' %> +@@includes.thirdpartyapi@@ + +### Additional information + +* You can find more details about how to obtain AWS Tokens to securely call AWS APIs and resources [here](@@base_url@@/aws#2). +* Checkout a complete sample on GitHub: diff --git a/docs/server-apis/azure-blob-storage.md b/docs/server-apis/azure-blob-storage.md new file mode 100644 index 00000000..a6866157 --- /dev/null +++ b/docs/server-apis/azure-blob-storage.md @@ -0,0 +1,44 @@ +--- +title: Azure Blob Storage Addon +lodash: true +--- +<% configuration.thirdParty = 'Azure Blob Storage' %> +@@includes.thirdpartyapi@@ + +### Additional information + +Here's a sample call to the delegation endpoint to get the SAS: + +``` +POST https://@@account.namespace@@/delegation +Content-Type: 'application/json' +{ + "client_id": "@@account.clientId@@", + "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", + "id_token": "{YOUR_ID_TOKEN}", + "target": "@@account.clientId@@", + "api_type": "azure_blob", + "scope": "openid" +} +``` + +* The `client_id` value identifies the requesting app (e.g. your website) and `{YOUR_ID_TOKEN}` identifies the user you are requesting this on behalf-of. (Notice that the `id_token` is signed with the `client_id` corresponding `clientSecret`). +* The `target` parameter identifies this API endpoint in Auth0 (often the same as `{CLIENT ID}`. This is the `client_id` of the app where this add-on has been enabled. +* `api_type` must be `azure_blob`. +* `scope` must be `openid`. + +The result of calling the delegation endpoint will be something like: + +``` +{ + "azure_blob_sas": "st=2015-01-08T18%3A45%3A14Z&se=2015-01-08T18%3A50%3A14Z&sp=r&sv=2014-02-14&sr=b&sig=13ABC456..." +} +``` + +You can use the blob SAS token either by appending it to a url directly or by passing it to one of the Azure Storage SDKs. + +``` +GET https://{STORAGEACCOUNT}.blob.core.windows.net/mycontainer/myblob.txt?st=2015-01-08T18%3A45%3A14Z&se=2015-01-08T18%3A50%3A14Z&sp=r&sv=2014-02-14&sr=b&sig=13ABC456... +``` + +![](https://docs.google.com/drawings/d/1aTHLCUPT4fCOXgX6fvUpxJdzd_rH_VzayBkLwLkwOBk/pub?w=784&h=437) diff --git a/docs/server-apis/azure-mobile-services.md b/docs/server-apis/azure-mobile-services.md new file mode 100644 index 00000000..0a78a7e2 --- /dev/null +++ b/docs/server-apis/azure-mobile-services.md @@ -0,0 +1,97 @@ +--- +title: Azure Mobile Services +lodash: true +--- +<% configuration.thirdParty = 'Azure Mobile Services' %> +@@includes.thirdpartyapi@@ + +### Additional Information + +#### 1. Create a client for the application + +WAMS endpoints can be used from anywhere: [Android](android-tutorial), [iOS](ios-tutorial), Windows 8 [C#](win8-cs-tutorial) or [JavaScript](win8-tutorial) and [Windows Phone](windowsphone-tutorial). You can use any of these tutorials for configuring your app that will interact with WAMS. + +A very good starting point is any of the samples you can download from the Azure Portal. Download and follow these: + +![](//cdn.auth0.com/docs/img/wams-tutorial-4.png) + +#### 2. Changing the sample to use Auth0 + +Changing the samples to use Auth0 is very simple. As an example, if you followed the Windows 8 sample (C#), you will end up with an `AuthenticateAsync` method that adds one of the standard WAMS authentication mechanisms. + + +``` +private async System.Threading.Tasks.Task AuthenticateAsync() +{ + while (user == null) + { + string message; + + try + { + //Adding Auth0 + //Login User + var auth0 = new Auth0Client("@@account.namespace@@", "@@account.clientId@@"); + + var appUser = await auth0.LoginAsync(); //This call presents user with all available options + + //This obtains a token for WAMS + var api = await auth0.GetDelegationToken("{THE WAMS CLIENT ID IN AUTH0}"); + + //Tell WAMS to use this new token + user = new MobileServiceUser(appUser.Profile["name"].ToString()); + user.MobileServiceAuthenticationToken = api["id_token"].ToString(); + App.MobileService.CurrentUser = user; + + //Old code + //user = await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.Facebook); + + message = + string.Format("You are now logged in - {0}", user.UserId); + } + + catch (InvalidOperationException) + { + message = "You must log in. Login Required"; + } + + var dialog = new MessageDialog(message); + + dialog.Commands.Add(new UICommand("OK")); + + await dialog.ShowAsync(); + } +} + +``` + +These 6 new lines of code do all the work for you. The important aspects: + +1. The `Auth0Client` class takes 2 parameters: your `namespace` and the `clientId` of the client application. +2. There are various overloads for the `LoginAsync` method. In the example above all options will be presented to the user. You can use other versions of `LoginAsync` to direct login to a specific provider. For example: `LoginAsync("github")` will have users login exclusively with GitHub. +3. The `GetDelegationToken` call exchanges the client token (just received in step #2) for another token to be used for with WAMS. +4. A new `MobileServiceUser` object is created with the new information. + +The input for the `GetDelegationToken` method, is the clientID of the App / API where `Windows Azure Mobile Services (WAMS) API` addon was enable. + +You might wonder why step #3 is necessary. This abstraction layer allows your client app to interact with multiple WAMS APIs (or even other APIs altogether). You can control in Auth0 which clients can call which API. You could, as an example, login a user with GitHub, then connect him to WAMS, and also interact with an AWS hosted endpoint. The delegation call allows you to flow the identity of the user across multiple environments in a clean, secure way. + +> The sample above is using Windows 8, C#. Clients on other platforms would use almost identical code. + +#### 3. Using the user identity in the WAMS backend +The final step is to use the information on the token on the server code. You will likely have to do 2 things: + +##### 1. Change permissions on the table for each operation: + +![](//cdn.auth0.com/docs/img/wams-tutorial-5.png) + + +##### 2. Use the `user` object to change the behavior of the operation. + +This example, inserts the `userId` on new rows: + +![](//cdn.auth0.com/docs/img/wams-tutorial-6.png) + +And then when querying, it filters out rows for the logged in user: + +![](//cdn.auth0.com/docs/img/wams-tutorial-7.png) diff --git a/docs/server-apis/azure-sb.md b/docs/server-apis/azure-sb.md new file mode 100644 index 00000000..1fc2a1e8 --- /dev/null +++ b/docs/server-apis/azure-sb.md @@ -0,0 +1,37 @@ +--- +lodash: true +--- +<% configuration.thirdParty = 'Azure Service Bus' %> +@@includes.thirdpartyapi@@ + +### Additional information + +Here's a sample call to the delegation endpoint to get the SAS: + +``` +POST https://@@account.namespace@@/delegation +Content-Type: 'application/json' +{ + "client_id": "@@account.clientId@@", + "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", + "id_token": "{YOUR_ID_TOKEN}", + "target": "@@account.clientId@@", + "api_type": "azure_sb", + "scope": "openid" +} +``` + +* The `client_id` value identifies the requesting app (e.g. your website) and `{YOUR_ID_TOKEN}` identifies the user you are requesting this on behalf-of. (Notice that the `id_token` is signed with the `client_id` corresponding `clientSecret`). +* The `target` parameter identifies this API endpoint in Auth0 (often the same as `{CLIENT ID}`. This is the `client_id` of the app where this add-on has been enabled. +* `api_type` must be `azure_sb`. +* `scope` must be `openid`. + +The result of calling the delegation endpoint will be something like: + +``` +{ + "azure_sb_sas": "SharedAccessSignature sig=k8bNfT81R8L...LztXvY%3D&se=14098336&skn=PolicyName&sr=http%3A%2F%2Fnamespace.servicebus.windows.net%2Fmy_queue" +} +``` + +![](https://docs.google.com/drawings/d/1aTHLCUPT4fCOXgX6fvUpxJdzd_rH_VzayBkLwLkwOBk/pub?w=784&h=437) diff --git a/docs/server-apis/firebase.md b/docs/server-apis/firebase.md new file mode 100644 index 00000000..d1f1d33c --- /dev/null +++ b/docs/server-apis/firebase.md @@ -0,0 +1,6 @@ +--- +title: Firebase +lodash: true +--- +<% configuration.thirdParty = 'Firebase' %> +@@includes.thirdpartyapi@@ diff --git a/docs/server-apis/golang.md b/docs/server-apis/golang.md new file mode 100644 index 00000000..0b4c2797 --- /dev/null +++ b/docs/server-apis/golang.md @@ -0,0 +1,82 @@ +--- +lodash: true +--- + +## GoLang API Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing Go Programming Language app to use it with Auth0.** + +### 1. Install `go-jwt-middleware` dependency + +Install `go-jwt-middleware` to check for JWTs on HTTP requests. + +Just run the following code to install the dependency + +````js +go get github.com/auth0/go-jwt-middleware +``` + +### 2. Configure `go-jwt-middleware` to use your Auth0 Account + +You need to set the ClientSecret in `go-jwt-middleware`'s configuration so that it can validate [JWTs](@@base_url@@/jwt) for you. + +````go +package main + +import ( + // ... + "github.com/auth0/go-jwt-middleware" + // ... +) + +func main() { + jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ + ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { + decoded, err := base64.URLEncoding.DecodeString("@@account.clientSecret@@") + if err != nil { + return nil, err + } + return decoded, nil + }, + }) +} +``` + +### 3. Secure your API + +Now, you can use the `go-jwt-middleware` to secure your API. You can do so using `net/http` handlers or using `negroni` middlewares as well. + +````go +// Regular HTTP HandlerFunc +func SecuredPingHandler(w http.ResponseWriter, r *http.Request) { + respondJson("All good. You only get this message if you're authenticated", w) +} + +// Negroni sample +r := mux.NewRouter() +r.Handle("/secured/ping", negroni.New( + negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), + negroni.Wrap(http.HandlerFunc(SecuredPingHandler)), +)) +http.Handle("/", r) +http.ListenAndServe(":3001", nil) + +// net/http sample +app := jwtMiddleware.Handler(SecuredPingHandler) +http.ListenAndServe("0.0.0.0:3000", app) +``` + +### 4. You're done! + +Now you have both your FrontEnd and Backend configured to use Auth0. Congrats, you're awesome! diff --git a/docs/server-apis/java-spring-security.md b/docs/server-apis/java-spring-security.md new file mode 100644 index 00000000..2e5ded8f --- /dev/null +++ b/docs/server-apis/java-spring-security.md @@ -0,0 +1,99 @@ +--- +lodash: true +--- + +## Java API Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing Java app to use it with Auth0.** + +### 1. Add Auth0 Spring Security dependency + +You need to add the `spring-security-auth0` dependency. + +For that, you can just add it to your `pom.xml` if you're using maven. + +````xml + + com.auth0 + spring-security-auth0 + 0.2 + +``` + +### 2. Configure Spring to use Auth0 + +Now you need to configure Spring to use Spring Security with Auth0. + +For that, just add the following to the `application-context.xml` + +````xml + + + + + + + + +``` + +and create the `auth0.properties` file with the following information: + +````properties +auth0.clientSecret=@@account.clientSecret@@ +auth0.clientId=@@account.clientId@@ +auth0.domain=@@account.namespace@@ +# This is the path to secure. +auth0.securedRoute=/secured/** +``` + +### 3. Create the controllers + +Now, you can create the controllers. Every controller that has a route inside `/secured/` in this case will ask for the JWT + +````java +@Controller +public class SecuredPingController { + + @RequestMapping(value = "/secured/ping") + @ResponseBody + public String securedPing() { + return "All good. You only get this message if you're authenticated"; + } +} +``` + +### 4. You're done! + +Now you have both your FrontEnd and Backend configured to use Auth0. Congrats, you're awesome! + +### Optional Steps +#### Configure CORS + +In order to configure CORS, just add the following `Filter` for all your requests: + +````java +@Component +public class SimpleCORSFilter implements Filter { + + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { + HttpServletResponse response = (HttpServletResponse) res; + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); + response.setHeader("Access-Control-Max-Age", "3600"); + response.setHeader("Access-Control-Allow-Headers", "Authorization"); + chain.doFilter(req, res); + } + + public void init(FilterConfig filterConfig) {} + + public void destroy() {} +``` diff --git a/docs/server-apis/java.md b/docs/server-apis/java.md new file mode 100644 index 00000000..7c0da8d7 --- /dev/null +++ b/docs/server-apis/java.md @@ -0,0 +1,117 @@ +--- +lodash: true +--- + +## Java API Tutorial + + + +Then, you just need to specify your Auth0 account configuration as enviroment variables. [Check it here](https://github.com/auth0/auth0-java/blob/master/examples/java-api/README.md#running-the-example) + +**Otherwise, Please follow the steps below to configure your existing Java app to use it with Auth0.** + +### 1. Add java-jwt dependency + +You need to add the `java-jwt` and `commons-codec` dependencies. + +For that, you can just add them to your `pom.xml` if you're using maven. + +````xml + + com.auth0 + java-jwt + 2.0.1 + + + commons-codec + commons-codec + 1.4 + +``` + +### 2. Add JWT Validation filter + +Now, you need to validate the [JWT](@@base_url@@/jwt). For that, we'll use a Filter. + +````java +@WebFilter(filterName= "jwt-filter", urlPatterns = { "/api/*" }) +public class JWTFilter implements Filter { + private JWTVerifier jwtVerifier; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + jwtVerifier = new JWTVerifier( + new Base64(true).decodeBase64("<%= account.clientSecret %>"), + "<%= account.clientId %>"); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + String token = getToken((HttpServletRequest) request); + + try { + Map decoded = jwtVerifier.verify(token); + // Do something with decoded information like UserId + chain.doFilter(request, response); + } catch (Exception e) { + throw new ServletException("Unauthorized: Token validation failed", e); + } + } + + private String getToken(HttpServletRequest httpRequest) throws ServletException { + String token = null; + final String authorizationHeader = httpRequest.getHeader("authorization"); + if (authorizationHeader == null) { + throw new ServletException("Unauthorized: No Authorization header was found"); + } + + String[] parts = authorizationHeader.split(" "); + if (parts.length != 2) { + throw new ServletException("Unauthorized: Format is Authorization: Bearer [token]"); + } + + String scheme = parts[0]; + String credentials = parts[1]; + + Pattern pattern = Pattern.compile("^Bearer$", Pattern.CASE_INSENSITIVE); + if (pattern.matcher(scheme).matches()) { + token = credentials; + } + return token; + } + + @Override + public void destroy() { + + } + +} +``` + +Please note that we're setting the URL Pattern to `/api/*` in this case. That means that we'll check the user is authenticated only if the request is to the API. + +Please note that if you're using Servlets 2.5, you won't be able to use the `@WebFilter` annotation. In that case, add the following to your `web.xml`: + +````xml + + JWTFilter + com.auth0.example.JWTFilter + + + + + JWTFilter + /api/* + +``` + + +### 4. You're done! + +Now you have both your FrontEnd and Backend configured to use Auth0. Congrats, you're awesome! diff --git a/docs/server-apis/nodejs.md b/docs/server-apis/nodejs.md new file mode 100644 index 00000000..510171c8 --- /dev/null +++ b/docs/server-apis/nodejs.md @@ -0,0 +1,55 @@ +--- +lodash: true +--- + +## NodeJS API Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing NodeJS app to use it with Auth0.** + +### 1. Add express-jwt dependency + +You need to add the express-jwt dependency. + +Just run the following code to install the dependency and add it to your `package.json` + +````js +npm install express-jwt --save +``` + +### 2. Configure express-jwt with your Auth0 account + +You need to set the ClientID and ClientSecret in `express-jwt`'s configuration so that it can validate and sign [JWT](@@base_url@@/jwt)s for you. + +````js + var express = require('express'); + var app = express(); + var jwt = require('express-jwt'); + + var jwtCheck = jwt({ + secret: new Buffer('<%= account.clientSecret %>', 'base64'), + audience: '<%= account.clientId %>' + }); +``` + +### 3. Secure your API + +Now, you need to specify one or more routes or paths that you want to protect, so that only users with the correct JWT will be able to do the request. + +````js +app.use('/api/path-you-want-to-protect', jwtCheck); +``` + +### 4. You're done! + +Now you have both your FrontEnd and Backend configured to use Auth0. Congrats, you're awesome! diff --git a/docs/server-apis/php-laravel.md b/docs/server-apis/php-laravel.md new file mode 100644 index 00000000..96f3c1dc --- /dev/null +++ b/docs/server-apis/php-laravel.md @@ -0,0 +1,119 @@ +--- +lodash: true +--- + +## PHP Laravel API Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing PHP Laravel app to use it with Auth0.** + +### 1. Add the needed dependencies and configure `composer.json` + +We need to add **laravel-auth0** dependency to your composer.json. As it depends on some packages that don't yet have tags (firebase/php-jwt for example), you need to first change your `composer.json` `minimum-stability` property to dev. + +Once that's done, just run the following: + +````bash +composer require auth0/login:1.0.* +``` + +> This sample uses **[Composer](https://getcomposer.org/doc/00-intro.md)**, a tool for dependency management in PHP. It allows you to declare the dependent libraries your project needs and it will install them in your project for you. + +### 2. Enable Auth0 in Laravel API + +Add the following in the list of the services `providers`, located in `app/config/app.php` + +````php +'providers' => array( + // ... + 'Auth0\Login\LoginServiceProvider', +); +``` + +Optionally, if you want to use the (facade)[http://laravel.com/docs/facades] called Auth0 you should also add an `alias` in the same file. That lets you call the service method like `Auth0::jwtuser()`. + +````php +'aliases' => array( + // ... + 'Auth0' => 'Auth0\Login\Facade\Auth0' +); +``` + +### 3. Configure it + +To configure the plugin, you need to publish the plugin configuration by executing the following command + +````bash +php artisan config:publish auth0/login +``` + +and then modify the config file `app/config/packages/auth0/login/api.php` using your Auth0 app credentials. + +````php +return array( + 'audience' => '@@account.clientId@@', + 'secret' => '@@account.clientSecret@@', +); +``` + +### 4. Configure APACHE + +By default, Apache doesn't provide the `Authorization header` to the request, we can solve that by enabling `mod_rewrite` and adding the following rule to your `.htaccess`: + +````bash +RewriteCond %{HTTP:Authorization} ^(.*) +RewriteRule .* - [e=HTTP_AUTHORIZATION:%1] +``` + +### 5. Use it & Run it + +Now you can secure your REST calls like this: + +````php +Route::get('/api/protected', array('before'=>'auth-jwt', function() { + return "Hello " . Auth0::jwtuser()->name; +})); +``` + +You can run the server by doing `php artisan serve --port=3001` to try all this out. + +### 6. You're done! + +Now you have both your FrontEnd and Backend configured to use Auth0. Congrats, you're awesome! + +### Options Steps +#### Configure CORS + +To configure CORS, you should add the `laravel-cors` dependency. You can [check it out here](https://github.com/barryvdh/laravel-cors). + +After you've installed it, just set the following in the configuration file for `CORS`: + +````php +'defaults' => array( + 'supportsCredentials' => false, + 'allowedOrigins' => array(), + 'allowedHeaders' => array(), + 'allowedMethods' => array(), + 'exposedHeaders' => array(), + 'maxAge' => 0, + 'hosts' => array(), +), + +'paths' => array( + '*' => array( + 'allowedOrigins' => array('*'), + 'allowedHeaders' => array('Content-Type', 'Authorization', 'Accept'), + 'allowedMethods' => array('POST', 'PUT', 'GET', 'DELETE') + ), +), +``` diff --git a/docs/server-apis/php-symfony.md b/docs/server-apis/php-symfony.md new file mode 100644 index 00000000..ca9760ff --- /dev/null +++ b/docs/server-apis/php-symfony.md @@ -0,0 +1,108 @@ +# Symfony Auth0 JWT Bundle +This bundle helps you integrate your Symfony WebApp with [Auth0](https://auth0.com/) to achieve Single Sign On with a few simple steps. You can see an example of usage [here](https://github.com/auth0/jwt-auth-bundle/tree/master/example) + +##Tutorial + +###1. Install dependencies + +We recommend using [Composer](http://getcomposer.org/doc/01-basic-usage.md) to install the library. + +Modify your `composer.json` to add the following dependencies and run `composer update`. + +~~~js +{ + "require": { + "firebase/php-jwt": "dev-master", + "adoy/oauth2": "dev-master", + "auth0/jwt-auth-bundle": "1.0.0" + } +} +~~~ + +###2. Add the bundle to your AppKernell.php file + +~~~php + +class AppKernel extends Kernel +{ + public function registerBundles() + { + $bundles = array( + + ... + + new \Auth0\JWTAuthBundle\Auth0JWTAuthBundle(), + + ... + + ); + + ... + + return $bundles; + } + +~~~ + +###3. Configure your Auth0 app data + +Modify the file /app/config/config.yml + +~~~yml +auth0_jwt_auth: + domain: yourdomain.auth0.com + client_id: YOURCLIENTID + client_secret: YOURCLIENTSECRET +~~~ + +###4. Setup your User and UserProvider + +Create your User and UserProvider. + +The UserProvider must implements the JWTUserProviderInterface (see /source/AppBundle/Security/A0UserProvider). This class should implement 2 methods: + +- loadUserByJWT: This method receives the decoded JWT (but overloaded with the encoded token on the token attribute) and should return a User. + +- getAnonymousUser: This method should return an anonymous user that represents an unauthenticated one (usually represented by the role *IS_AUTHENTICATED_ANONYMOUSLY*). +*Both methods can throw an AuthenticationException exception in case that the user is not found, in the case of the loadUserByJWT method, or you don't want to handle unauthenticated users on your app, in the case of the getAnonymousUser method.* + +Then configure your UserProvider on /app/config/services.yml + +~~~yml +services: + a0_user_provider: + class: AppBundle\Security\A0UserProvider + arguments: ["@auth0_jwt_auth.auth0_service"] +~~~ + +###5. Setup the SecurityProvider + +Modify the file /app/config/security.yml: + +- define your user provider +- define your secured area that want to authenticate using JWT +- define the access_control section with the roles needed for each route + +~~~yml +security: + providers: + a0: + id: + a0_user_provider + + firewalls: + secured_area: + pattern: ^/api + stateless: true + simple_preauth: + authenticator: auth0_jwt_aut.jwt_authenticator + + access_control: + - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { path: ^/api, roles: ROLE_OAUTH_USER } +~~~ + + +## Issue Reporting + +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](https://auth0.com/whitehat) details the procedure for disclosing security issues. diff --git a/docs/server-apis/php.md b/docs/server-apis/php.md new file mode 100644 index 00000000..34fa5066 --- /dev/null +++ b/docs/server-apis/php.md @@ -0,0 +1,118 @@ +--- +lodash: true +--- + +## PHP API Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing PHP app to use it with Auth0.** + +### 1. Add the needed dependencies + +We need 2 dependencies to make this work: + +* **php-jwt**: this will take care of checking the JWT +* **router**: we'll use this for creating simple routes + +````json +{ + "name": "your/app", + "description": "Basic sample for securing an API", + "require": { + "bramus/router": "dev-master", + "firebase/php-jwt": "dev-master" + }, + "license": "MIT" +} +``` +> This sample uses **[Composer](https://getcomposer.org/doc/00-intro.md)**, a tool for dependency management in PHP. It allows you to declare the dependent libraries your project needs and it will install them in your project for you. + +### 2. Create the JWT Validation filter + +Now, you need to validate the [JWT](@@base_url@@/jwt). For that, we'll create a filter that will run in the routes we need. + +````php + // Require composer autoloader + require __DIR__ . '/vendor/autoload.php'; + + // Create Router instance + $router = new \Bramus\Router\Router(); + + // Check JWT on /secured routes. This can be any route you like + $router->before('GET', '/secured/.*', function() { + + // This method will exist if you're using apache + // If you're not, please go to the extras for a defintion of it. + $requestHeaders = apache_request_headers(); + $authorizationHeader = $requestHeaders['AUTHORIZATION']; + + if ($authorizationHeader == null) { + header('HTTP/1.0 401 Unauthorized'); + echo "No authorization header sent"; + exit(); + } + + // // validate the token + $token = str_replace('Bearer ', '', $authorizationHeader); + $secret = '@@account.clientSecret@@'; + $decoded_token = null; + try { + $decoded_token = JWT::decode($token, base64_decode(strtr($secret, '-_', '+/')) ); + } catch(UnexpectedValueException $ex) { + header('HTTP/1.0 401 Unauthorized'); + echo "Invalid token"; + exit(); + } + + + // // validate that this token was made for us + if ($decoded_token->aud != '@@account.clientId@@0') { + header('HTTP/1.0 401 Unauthorized'); + echo "Invalid token"; + exit(); + } + + }); +``` + +### 3. Create a /secured route that will use this filter + +Now, you can just create routes under /secured route which will check the JWT + +````php +// Controllers API + +$router->get('/ping', function() { + echo "All good. You don't need to be authenticated to call this"; +}); + +$router->get('/secured/ping', function() { + echo "All good. You only get this message if you're authenticated"; +}); + +// Run the Router +$router->run(); +``` + +### 4. You're done! + +Now you have both your FrontEnd and Backend configured to use Auth0. Congrats, you're awesome! + +### Options Steps +#### Configure CORS + +You can configure CORS, by just adding [this lines](https://github.com/auth0/auth0-PHP/blob/master/examples/basic-api/index.php#L45-L54) to your `index.php` + +#### Define `apache_request_headers` if not available + +If the function is not available, just [copy this lines](https://github.com/auth0/auth0-PHP/blob/master/examples/basic-api/index.php#L8-L29) to your `index.php` diff --git a/docs/server-apis/python.md b/docs/server-apis/python.md new file mode 100644 index 00000000..7daf764e --- /dev/null +++ b/docs/server-apis/python.md @@ -0,0 +1,125 @@ +--- +lodash: true +--- + +## Python API Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing Python app to use it with Auth0.** + +### 1. Add the needed dependencies + +In this example, we'll be using Flask and we'll be validating the JWT. For that, add the following dependencies to your `requirements.txt`. + +````text +flask +PyJWT +flask-cors +``` + +### 2. Create the JWT Validation annotation + +Now, you need to validate the [JWT](@@base_url@@/jwt). For that, we'll create a custom annotation. + +````python +import jwt +import base64 +import os + +from functools import wraps +from flask import Flask, request, jsonify, _request_ctx_stack +from werkzeug.local import LocalProxy +from flask.ext.cors import cross_origin + +app = Flask(__name__) +# Authentication annotation +current_user = LocalProxy(lambda: _request_ctx_stack.top.current_user) + +# Authentication attribute/annotation +def authenticate(error): + resp = jsonify(error) + + resp.status_code = 401 + + return resp + +def requires_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + auth = request.headers.get('Authorization', None) + if not auth: + return authenticate({'code': 'authorization_header_missing', 'description': 'Authorization header is expected'}) + + parts = auth.split() + + if parts[0].lower() != 'bearer': + return {'code': 'invalid_header', 'description': 'Authorization header must start with Bearer'} + elif len(parts) == 1: + return {'code': 'invalid_header', 'description': 'Token not found'} + elif len(parts) > 2: + return {'code': 'invalid_header', 'description': 'Authorization header must be Bearer + \s + token'} + + token = parts[1] + try: + payload = jwt.decode( + token, + base64.b64decode('@@account.clientSecret@@'.replace("_","/").replace("-","+")) + ) + except jwt.ExpiredSignature: + return authenticate({'code': 'token_expired', 'description': 'token is expired'}) + except jwt.DecodeError: + return authenticate({'code': 'token_invalid_signature', 'description': 'token signature is invalid'}) + + if payload['aud'] != '@@account.clientId@@': + return authenticate({'code': 'invalid_audience', 'description': 'the audience does not match. expected: ' + CLIENT_ID}) + + _request_ctx_stack.top.current_user = user = payload + return f(*args, **kwargs) + + return decorated +``` + +### 3. Use this annotation in your methods + +Now, you can just use this annotation in your methods + +````python +# Controllers API + +# This doesn't need authenticatton +@app.route("/ping") +@cross_origin(headers=['Content-Type', 'Authorization']) +def ping(): + return "All good. You don't need to be authenticated to call this" + +# This does need authentication +@app.route("/secured/ping") +@cross_origin(headers=['Content-Type', 'Authorization']) +@requires_auth +def securedPing(): + return "All good. You only get this message if you're authenticated" +``` + +### 4. You're done! + +Now you have both your FrontEnd and Backend configured to use Auth0. Congrats, you're awesome! + +### Note for Python 2.7 + +If you're using Python 2.7 and you already have the `jwt` package installed, you need to do the following to get this working: + +````bash +pip uninstall jwt +pip uninstall pyjwt +pip install pyjwt +``` diff --git a/docs/server-apis/rails.md b/docs/server-apis/rails.md new file mode 100644 index 00000000..8e9e1f17 --- /dev/null +++ b/docs/server-apis/rails.md @@ -0,0 +1,93 @@ +--- +lodash: true +--- + +## Ruby on Rails API Tutoial + + + + +**Otherwise, Please follow the steps below to configure your existing Ruby on Rails app to use it with Auth0.** + +### 1. Add jwt dependency to your Gemfile + +You need to add the jwt dependency. + +Open your Gemfile and add the following: + +````bash +gem 'jwt' +``` + +### 2. Add your Auth0 account information to secrets.yml + +You need to set the ClientID and ClientSecret in `config/secrets.yml` file so that you can then get them and use them to validate and sign [JWT](@@base_url@@/jwt)s for you. + +````yaml +development: + secret_key_base: 3342e9faedd8fe9ea360c0af568d00a46917923791c23e144d66849b272d2ff63e743f9bb209dab7d6e732bb5f919e46e3fe552b8919140805bb89c346e68876, + auth0_client_id: <%= account.clientId %> + auth0_client_secret: <%= account.clientSecret %> + +production: + secret_key_base: ENV["SECRET_KEY_BASE"] + auth0_client_id: <%= account.clientId %> + auth0_client_secret: <%= account.clientSecret %> +``` + +### 3. Create SecuredController to validate the JWT + +Now, let's add a new controller that inherits from ApplicationController which will take care of validating the JWT and checking that the user has been authenticated. + +````ruby +class SecuredController < ApplicationController + before_action :validate_token + + class InvalidTokenError < StandardError; end + + private + + def validate_token + begin + authorization = request.headers['Authorization'] + raise InvalidTokenError if authorization.nil? + + token = request.headers['Authorization'].split(' ').last + decoded_token = JWT.decode(token, + JWT.base64url_decode(Rails.application.secrets.auth0_client_secret)) + + raise InvalidTokenError if Rails.application.secrets.auth0_client_id != decoded_token[0]["aud"] + rescue JWT::DecodeError, InvalidTokenError + render text: "Unauthorized", status: :unauthorized + end + end + +end +``` + +### 4. Securing your API + +Now, every new controller that you create that inherits from `SecuredController` will verify that the user is authenticated. + +````ruby +class SecuredPingController < SecuredController + + def ping + render text: "All good. You only get this message if you're authenticated" + end + +end +``` + +### 5. You're done! + +Now you have both your FrontEnd and Backend configured to use Auth0. Congrats, you're awesome! diff --git a/docs/server-apis/ruby.md b/docs/server-apis/ruby.md new file mode 100644 index 00000000..fc2249dc --- /dev/null +++ b/docs/server-apis/ruby.md @@ -0,0 +1,48 @@ +--- +lodash: true +--- + +## Ruby API Tutorial + +> Note: If you're creating a Ruby On Rails app, please check [this other tutorial](@@base_url@@/new/server-apis/rails). + +Otherwise, Please follow the steps below to configure your existing Ruby app to use it with Auth0. + +### 1. Add jwt dependency to your Gemfile + +You need to add the jwt dependency. + +Open your Gemfile and add the following: + +```bash +gem 'jwt' +``` + +### 2. Validate JWT token + +You need to validate the [JWT](@@base_url@@/jwt)s to make sure the user is authenticated. For that, in a filter or in a middleware processor that runs before your actions, you should write the following code: + +```ruby +class InvalidTokenError < StandardError; end + +def validate_token + begin + auth0_client_id = '<%= account.clientId %>' + auth0_client_secret = '<%= account.clientSecret %>' + authorization = request.headers['Authorization'] + raise InvalidTokenError if authorization.nil? + + token = request.headers['Authorization'].split(' ').last + decoded_token = JWT.decode(token, + JWT.base64url_decode(auth0_client_secret)) + + raise InvalidTokenError if auth0_client_id != decoded_token[0]["aud"] + rescue JWT::DecodeError + raise InvalidTokenError + end +end +``` + +### 3. You're done! + +Now you have both your FrontEnd and Backend configured to use Auth0. Congrats, you're awesome! diff --git a/docs/server-apis/salesforce-sandbox.md b/docs/server-apis/salesforce-sandbox.md new file mode 100644 index 00000000..25d1fa8f --- /dev/null +++ b/docs/server-apis/salesforce-sandbox.md @@ -0,0 +1,14 @@ +--- +title: Salesforce (Sandbox) API +lodash: true +--- +<% configuration.thirdParty = 'Salesforce (Sandbox)' %> +@@includes.thirdpartyapi@@ + +### Additional information + +Auth0 supports both the __production__ connection to Salesforce and the __Sandbox__, the only difference being the endpoints hosted by Salesforce: `https://login.salesforce.com` and `https://test.salesforce.com` respectively. + +> Under the hood, Auth0 uses [OAuth 2.0 JWT Bearer Token Flow](https://help.salesforce.com/HTViewHelpDoc?id=remoteaccess_oauth_jwt_flow.htm&language=en_US) to obtain an `access_token`. All details of construction of the right JWT are taken care of by Auth0. + +![](https://docs.google.com/drawings/d/1aTHLCUPT4fCOXgX6fvUpxJdzd_rH_VzayBkLwLkwOBk/pub?w=784&h=437) diff --git a/docs/server-apis/salesforce.md b/docs/server-apis/salesforce.md new file mode 100644 index 00000000..ffa0f58f --- /dev/null +++ b/docs/server-apis/salesforce.md @@ -0,0 +1,24 @@ +--- +title: Salesforce +lodash: true +--- +<% configuration.thirdParty = 'Salesforce' %> +@@includes.thirdpartyapi@@ + +### Additional information + +Auth0 supports both the __production__ connection to Salesforce and the __Sandbox__, the only difference being the endpoints hosted by Salesforce: `https://login.salesforce.com` and `https://test.salesforce.com` respectively. + +The integration also supports getting tokens for __Salesforce Community Sites__. For this to work, you need to pass two additional parameters: + +``` +... +community_name: 'mycommunity', +community_url_section: 'members' +... + +``` + +> Under the hood, Auth0 uses [OAuth 2.0 JWT Bearer Token Flow](https://help.salesforce.com/HTViewHelpDoc?id=remoteaccess_oauth_jwt_flow.htm&language=en_US) to obtain an `access_token`. All details of construction of the right JWT are taken care of by Auth0. + +![](https://docs.google.com/drawings/d/1aTHLCUPT4fCOXgX6fvUpxJdzd_rH_VzayBkLwLkwOBk/pub?w=784&h=437) diff --git a/docs/server-apis/sap-odata.md b/docs/server-apis/sap-odata.md new file mode 100644 index 00000000..849d7566 --- /dev/null +++ b/docs/server-apis/sap-odata.md @@ -0,0 +1,14 @@ +--- +title: SAP OData +lodash: true +--- +> This integration is in __experimental mode__. Contact us if you have questions. + +<% configuration.thirdParty = 'SAP OData' %> +@@includes.thirdpartyapi@@ + +### Additional information + +> Under the hood, Auth0 uses [SAML 2.0 Bearer Assertion Flow for OAuth 2.0](http://help.sap.com/saphelp_nw74/helpdata/en/12/41087770d9441682e3e02958997846/content.htm) to obtain an `access_token`. All details of construction of the right SAML token are taken care of by Auth0. + +![](https://docs.google.com/drawings/d/1cG4mJy742ZW1ixcMdh3XZmRPxRJldt5pax5ktfb6Ff4/pub?w=744&h=425) diff --git a/docs/server-apis/webapi-owin.md b/docs/server-apis/webapi-owin.md new file mode 100644 index 00000000..b2b5eb03 --- /dev/null +++ b/docs/server-apis/webapi-owin.md @@ -0,0 +1,138 @@ +--- +lodash: true +--- + +## ASP.NET Web API OWIN Tutorial + + + +**Otherwise, please follow the steps below to configure your existing ASP.NET Web API OWIN app to use it with Auth0.** + +### 1. Setup NuGet dependencies + +Update this package: +````Powershell +Update-Package System.IdentityModel.Tokens.Jwt +``` + +Install the following NuGet package: +````Powershell +Install-Package Microsoft.Owin.Security.Jwt +``` + +### 2. Configure Json Web Token authentication + +Open the **Startup.cs** class located inside the **App_Start** folder. + +Add the following using statements +````CSharp +using Microsoft.Owin.Security.Jwt; +using System.Web.Http; +using Microsoft.Owin.Security.DataHandler.Encoder; +using Microsoft.Owin.Security; +using WebConfigurationManager = System.Web.Configuration.WebConfigurationManager; +``` + +Update the `Configuration` method with the following code: +````CSharp +var issuer = WebConfigurationManager.AppSettings["Auth0Domain"]; +var audience = WebConfigurationManager.AppSettings["Auth0ClientID"]; +var secret = TextEncodings.Base64Url.Decode( + WebConfigurationManager.AppSettings["Auth0ClientSecret"]); + +// Api controllers with an [Authorize] attribute will be validated with JWT +app.UseJwtBearerAuthentication( + new JwtBearerAuthenticationOptions + { + AuthenticationMode = AuthenticationMode.Active, + AllowedAudiences = new[] { audience }, + IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[] + { + new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret) + }, + }); +``` + +### 3. Update the web.config file with your app's credentials +Open the **web.config** file located at the solution's root. + +Add the following entries as children of the `` element. +````xml + + + +``` + +### 4. Securing your API +All you need to do now is add the `[System.Web.Http.Authorize]` attribute to the controllers/actions for which you want to verify that users are authenticated. + +### 5. You've nailed it. + +Now you have both your FrontEnd and Backend configured to use Auth0. Congrats, you're awesome! + +### Optional Steps +#### Configuring CORS + +To configure CORS you need to first install the `Microsoft.Owin.Cors` NuGet package. Once that is done you can simply update the configuration method in your **Startup** class as shown in the following snippet: +````CSharp +app.UseCors(CorsOptions.AllowAll); +``` + +> Important: The CORS policy used in the previous code snippet is not recommended for a production. You will need to implement the `ICorsPolicyProvider` interface. + +#### Working with claims +If you want to read/modify the claims that are populated based on the JWT you can use the following extensibility points: +````CSharp +string token = string.Empty; + +// Api controllers with an [Authorize] attribute will be validated with JWT +app.UseJwtBearerAuthentication( + new JwtBearerAuthenticationOptions + { + AuthenticationMode = AuthenticationMode.Active, + AllowedAudiences = new[] { audience }, + IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[] + { + new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret) + }, + Provider = new OAuthBearerAuthenticationProvider + { + OnRequestToken = context => + { + token = context.Token; + return Task.FromResult(null); + }, + OnValidateIdentity = context => + { + if (!string.IsNullOrEmpty(token)) + { + var notPadded = token.Split('.')[1]; + var claimsPart = Convert.FromBase64String( + notPadded.PadRight(notPadded.Length + (4 - notPadded.Length % 4) % 4, '=')); + + var obj = JObject.Parse(Encoding.UTF8.GetString(claimsPart, 0, claimsPart.Length)); + + // simple, not handling specific types, arrays, etc. + foreach (var prop in obj.Properties().AsJEnumerable()) + { + if (!context.Ticket.Identity.HasClaim(prop.Name, prop.Value.Value())) + { + context.Ticket.Identity.AddClaim(new Claim(prop.Name, prop.Value.Value())); + } + } + } + + return Task.FromResult(null); + } + } + }); +``` diff --git a/docs/server-platforms/apache.md b/docs/server-platforms/apache.md new file mode 100644 index 00000000..5af329bb --- /dev/null +++ b/docs/server-platforms/apache.md @@ -0,0 +1,106 @@ +--- +lodash: true +--- + +## Apache Tutorial + +**Please follow the steps below to configure your application using Apache to work with Auth0 and Open ID Connect.** + +### 1. Install and enable `mod_auth_openidc` module + +First, you need to install the `mod_auth_openidc` module for Apache. + +You can get the binaries from [Github](https://github.com/pingidentity/mod_auth_openidc/releases) and install them for your OS. If your OS isn't compatible with any of the binaries, you can still [build it from source](https://github.com/pingidentity/mod_auth_openidc/blob/master/INSTALL) + +Once you've installed it, you just need to enable it for Apache + +````bash +sudo a2enmod mod_auth_openidc +``` + +### 2. Configure the module with your Auth0 Account information + +Now, you should get a new configuration file under the `/etc/apache2/mods-available` folder, where Apache modules are normally installed. + +In there, you must add the following configuration for the `mod_auth_openidc` module + +```` +OIDCProviderIssuer https://@@account.namespace@@ +OIDCProviderAuthorizationEndpoint https://@@account.namespace@@/authorize +OIDCProviderTokenEndpoint https://@@account.namespace@@/oauth/token +OIDCProviderTokenEndpointAuth client_secret_post +OIDCProviderUserInfoEndpoint https://@@account.namespace@@/userinfo + +OIDCClientID @@account.clientId@@ +OIDCClientSecret @@account.clientSecret@@ + +OIDCScope "openid profile" +OIDCRedirectURI https://your_apache_server/your_path/redirect_uri/ +OIDCCryptoPassphrase +OIDCCookiePath /your_path/ + +SSLEngine on +SSLCertificateFile /home/your_cert.crt +SSLCertificateKeyFile /home/your_key.key + + + AuthType openid-connect + Require valid-user + LogLevel debug + +``` + +### 3. Configuring Auth0 secret + +Auth0 `clientSecret` is by default Base64 encoded which isn't compatible with this Apache plugin out of the box. We're going to change that in the near future, but in the meantime, you need to call an API to set that, for your account, the `clientSecret` isn't Base64 encoded. + +Just do the following `curl` from your terminal. Make sure to change `ACCESS_TOKEN` with a token obtained here <@@base_url@@/api#!#post--oauth-token> + +````bash +curl 'https://@@account.namespace@@/api/clients/@@account.clientId@@' -X PUT -H 'authorization: Bearer ACCESS_TOKEN' -H 'content-type: application/json' --data-binary $'{ "jwtConfiguration": {"lifetimeInSeconds": "36000", "secretNotEncoded": true }}' +``` + +> Please note that you can get your `access_token` by clicking on `Try Me` in [this endpoint of the Api Explorer](@@base_url@@/api#!#post--oauth-token) + +### 4. Authorization + +You can configure Apache to protect a certain location based on an attribute of the user. Here is an example: + +``` + + AuthType openid-connect + #Require valid-user + Require claim folder:example + + + + AuthType openid-connect + #Require valid-user + Require claim folder:example2 + +``` + +Then you can write a rule in Auth0 that would return the `folder` attribute: + +``` +function(user, context, callback) { + if (somecondition()) { + user.folder = 'example2'; + } + + user.folder = 'example'; +} +``` + +Or you could even use an array of folders and the apache module will check if the array contains any of these values + +``` +function(user, context, callback) { + user.folders = []; + if (somecondition()) { + user.folders.push('example2'); + } + + user.folders.push('example'); +} +``` diff --git a/docs/server-platforms/asp-classic.md b/docs/server-platforms/asp-classic.md new file mode 100644 index 00000000..4414b912 --- /dev/null +++ b/docs/server-platforms/asp-classic.md @@ -0,0 +1,114 @@ +--- +lodash: true +--- + +## ASP Classic Tutorial + +**Please follow the steps below to configure your existing ASP.Net Classic WebApp to use it with Auth0.** + +### 1. Showing the Login Widget + +First, we need to create the `default.asp` which will show the Login Widget from Auth0. + +````asp +<%= '\<%@ Language="VBScript" %\>' %> +<%= '\<% Option Explicit %\>' %> + + + + + Testing Auth0 with Classic ASP + + + + + + + +``` + +After logging in with any provider, Auth0 will redirect the user to `/callback.asp`. + +### 2. Processing the callback response + +We need to add the handler for the Auth0 callback so that we can authenticate the user and get his information. For that, we'll create the `callback.asp` file. + +It will implement the basic OAuth 2 flow: + +1. Exchanges the **code** for an **access_token** +1. Calls the **Userinfo** endpoint to get the current logged in user profile using the access_token as credentials. + +````asp +<%= '\<%@ Language="VBScript" %\>' %> + + + +<%= '\<%' %> +CLIENT_ID = "@@account.clientId@@" +CLIENT_SECRET = "@@account.clientSecret@@" +REDIRECT_URI = "http://yourserver.com/callback.asp" + +AUTHORIZATION_CODE = Request.querystring( "code" ) + +access_token = GetAccessToken(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, AUTHORIZATION_CODE) + +set profile = GetUserProfile( access_token ) + + +'Here, you should save the profile in the session or somewhere' + +Response.Write "UserID = " & profile.user_id + + + +Function GetUserProfile(access_token) + + Set http = Server.CreateObject("MSXML2.ServerXMLHTTP") + + http.open "GET", "https://eugeniop.auth0.com/userinfo?access_token=" & access_token, False + + http.send + + profile = http.responseText + + Set GetUserProfile = JSON.parse(profile) + + Set http = Nothing + +End Function + + +Function GetAccessToken(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, code) + + Set http = Server.CreateObject("MSXML2.ServerXMLHTTP") + + http.open "POST", "https://eugeniop.auth0.com/oauth/token", False + + http.setRequestHeader "Content-Type", "application/x-www-form-urlencoded" + + http.send "client_id=" & CLIENT_ID & "&client_secret=" & CLIENT_SECRET & "&redirect_uri=" & server.UrlEncode(REDIRECT_URI) & "&code=" & AUTHORIZATION_CODE & "&grant_type=authorization_code" + + result = http.responseText + + Set http = Nothing + + set jsonResult = JSON.parse(result) + + GetAccessToken = jsonresult.access_token + +End Function + +<%= '%\>' %> +``` + +### 3. You're done! + +You have configured your ASP.Net Classic Webapp to use Auth0. Congrats, you're awesome! diff --git a/docs/server-platforms/aspnet-owin.md b/docs/server-platforms/aspnet-owin.md new file mode 100644 index 00000000..eb3b3245 --- /dev/null +++ b/docs/server-platforms/aspnet-owin.md @@ -0,0 +1,107 @@ +--- +lodash: true +--- + +# Using Auth0 with ASP.NET (OWIN) + + + + +This tutorial explains how to integrate Auth0 with an ASP.NET application (of any kind: WebForms, MVC and even Web API) that uses the ASP.NET 4.5 Owin infrastructure. + +## Tutorial + +### 1. Install Auth0-ASPNET-Owin NuGet package + +Use the NuGet Package Manager (Tools -> Library Package Manager -> Package Manager Console) to install the **Auth0-ASPNET-Owin** package, running the command: + +``` +Install-Package Auth0-ASPNET-Owin +``` + +### 2. Setting up the callback URL in Auth0 + +
      +

      After authenticating the user on Auth0, we will do a POST to your website. The first POST will be to the built-in OWIN route "/signin-auth0" (For security purposes, you have to register this URL on the Application Settings section on Auth0 Dashboard). After that is successful, it will redirect again to "/Auth0Account/ExternalLoginCallback" (Please do not register this route on the dashboard).

      + +
      http://localhost:PORT/signin-auth0
      +
      + +### 3. Filling Web.Config with your Auth0 settings + +The NuGet package also created three settings on ``. Replace those with the following settings: + +``` + + + +``` + +### 4. Configure authentication with Auth0 + +Edit `App_Start\Startup.Auth.cs` in order to call the `UseAuth0Authentication` extension method: + + public void ConfigureAuth(IAppBuilder app) + { + // Enable the application to use a cookie to store information for the signed in user + app.UseCookieAuthentication(new CookieAuthenticationOptions + { + AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, + LoginPath = new PathString("/Account/Login") + }); + + // Use a cookie to temporarily store information about a user logging in with a third party login provider + app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); + + // ... + + app.UseAuth0Authentication( + clientId: System.Configuration.ConfigurationManager.AppSettings["auth0:ClientId"], + clientSecret: System.Configuration.ConfigurationManager.AppSettings["auth0:ClientSecret"], + domain: System.Configuration.ConfigurationManager.AppSettings["auth0:Domain"]); + } + +The nuget provides a simple controller (_Auth0AccountController_) to process the authentication response from Auth0. If you want to use your own controller, make sure you set the `redirectPath` parameter. For example, in order to use the implementation provided by Visual Studio templates, use the following: `redirectPath: "/Account/ExternalLoginCallback"`. + +### 5. Triggering login manually or integrating the Auth0Lock + +@@lockSDK@@ + +### 6. Accessing user information + +Once the user is successfully authenticated with the application, a `ClaimsPrincipal` will be generated which can be accessed through the `Current` property: + + public ActionResult Index() + { + string email = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value; + } + +The user profile is normalized regardless of where the user came from. We will always include these: `user_id`, `name`, `email`, `nickname`, and `picture`. For more information about the user profile, see [this article](user-profile). + + +**Congratulations!** + +---- + +### More information + +#### Authorization + +You can use the declarative `[Authorize]`, `` in `web.config` or code-based checks like `User.Identity.IsAuthenticated`. + +#### Log out + +To clear the cookie generated on login, use the `HttpContext.GetOwinContext().Authentication.SignOut()` method. + +#### Download the sample + +Browse the sample on GitHub. diff --git a/docs/aspnet-tutorial.md b/docs/server-platforms/aspnet.md similarity index 66% rename from docs/aspnet-tutorial.md rename to docs/server-platforms/aspnet.md index 1f742021..c793f52e 100644 --- a/docs/aspnet-tutorial.md +++ b/docs/server-platforms/aspnet.md @@ -12,17 +12,19 @@ Use the NuGet Package Manager (Tools -> Library Package Manager -> Package Manag Install-Package Auth0-ASPNET ``` +> This package will add a `LoginCallback.ashx` to your project, which will process the login. + ### 2. Setting up the callback URL in Auth0
      -

      After authenticating the user on Auth0, we will do a POST to a URL on your web site. For security purposes, you have to register this URL on the Application Settings section on Auth0 Admin app.

      +

      After authenticating the user on Auth0, we will do a POST to a URL on your web site. For security purposes, you have to register this URL on the Application Settings section on Auth0 Admin app.

      http://localhost:PORT/LoginCallback.ashx
      ### 3. Filling Web.Config with your Auth0 settings -The NuGet package also created four settings on ``. Replace those with the following settings: +The NuGet package also created three settings on ``. Replace those with the following settings: ``` @@ -30,22 +32,21 @@ The NuGet package also created four settings on ``. Replace those w ``` -### 4. Triggering login manually or integrating the Auth0 widget +### 4. Triggering login manually or integrating the Auth0Lock -@@sdk@@ +@@lockSDK@@ ### 5. Accessing user information -Once the user succesfuly authenticated to the application, a `ClaimsPrincipal` will be generated which can be accessed through the `User` property or `Thread.CurrentPrincipal` +Once the user succesfuly authenticated to the application, a `ClaimsPrincipal` will be generated which can be accessed through the `Current` property: - public ActionResult Index() + public ActionResult Index() { - var claims = (User.Identity as IClaimsIdentity).Claims - string email = claims.SingleOrDefault(c => c.ClaimType == "email"); + string email = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value; } The user profile is normalized regardless of where the user came from. We will always include these: `user_id`, `name`, `email`, `nickname` and `picture`. For more information about the user profile [read this](user-profile). - + **Congratulations!** ---- @@ -58,7 +59,7 @@ You can use the usual authorization techniques since the `LoginCallback.ashx` ha #### Log out -To clear the cookie generated on login, use the `ClaimsCookie.ClaimsCookieModule.Instance.SignOut()` method. +To clear the cookie generated on login, use the `FederatedAuthentication.SessionAuthenticationModule.SignOut()` method on the `AccountController\Logout` method. #### Link accounts @@ -72,7 +73,7 @@ You will need the `access_token` of the logged in user. You can get it from: #### Flow the identity to a WCF service -If you want to flow the identity of the user logged in to a web site, to a WCF service or an API, you have to use the `scope=openid` parameter on the login (as shown in the example above). When sending that paramter, Auth0 will generate an `id_token` which is a [JsonWebToken](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06) that can be either send straight to your service or it can be exchanged to generate an `ActAs` token. . Notice that by default it will only include the user id as part of the claims. If you want to get the full claim set for the user, use `scope=openid%20profile`. [Read more about this](/wcf-tutorial). +If you want to flow the identity of the user logged in to a web site, to a WCF service or an API, you have to use the `callbackOnLocationHash: true` parameter on the login widget constructor. When sending that paramter, Auth0 will generate an `id_token` which is a [JsonWebToken](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06) that can be either send straight to your service or it can be exchanged to generate an `ActAs` token. [Read more about this](/wcf-tutorial). #### Manage environments: Dev, Test, Production diff --git a/docs/server-platforms/golang.md b/docs/server-platforms/golang.md new file mode 100644 index 00000000..336df340 --- /dev/null +++ b/docs/server-platforms/golang.md @@ -0,0 +1,208 @@ +--- +lodash: true +--- + +## GoLang Webapp Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing GoLang WebApp to use it with Auth0.** + +### 1. Add dependencies + +Install the following dependencies using `go get` + +````bash +go get github.com/gorilla/mux +go get golang.org/x/oauth2 +go get github.com/astaxie/beego/session +``` + +> This example uses `mux` for routing but you can use whichever router you want + +### 2. Add the Auth0 Callback Handler + +You'll need to create a callback handler that Auth0 will call once it redirects to your app. For that, you can do the following: + +````go +package callback + +import ( + // Don't forget this first import or nothing will work + _ "crypto/sha512" + "encoding/json" + "io/ioutil" + "net/http" + "os" + "golang.org/x/oauth2" +) + +func CallbackHandler(w http.ResponseWriter, r *http.Request) { + + domain := "@@account.namespace@@" + + // Instantiating the OAuth2 package to exchange the Code for a Token + conf := &oauth2.Config{ + ClientID: os.Getenv("AUTH0_CLIENT_ID"), + ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"), + RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"), + Scopes: []string{"openid", "profile"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://" + domain + "/authorize", + TokenURL: "https://" + domain + "/oauth/token", + }, + } + + // Getting the Code that we got from Auth0 + code := r.URL.Query().Get("code") + + // Exchanging the code for a token + token, err := conf.Exchange(oauth2.NoContext, code) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Getting now the User information + client := conf.Client(oauth2.NoContext, token) + resp, err := client.Get("https://" + domain + "/userinfo") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Reading the body + raw, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Unmarshalling the JSON of the Profile + var profile map[string]interface{} + if err := json.Unmarshal(raw, &profile); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Saving the information to the session. + // We're using https://github.com/astaxie/beego/tree/master/session + // The GlobalSessions variable is initialized in another file + // Check https://github.com/auth0/auth0-golang/blob/master/examples/regular-web-app/app/app.go + session, _ := app.GlobalSessions.SessionStart(w, r) + defer session.SessionRelease(w) + + session.Set("id_token", token.Extra("id_token")) + session.Set("access_token", token.AccessToken) + session.Set("profile", profile) + + // Redirect to logged in page + http.Redirect(w, r, "/user", http.StatusMovedPermanently) + +} +``` + +Remember to set this handler to the `/callback` path: + +````go +r := mux.NewRouter() +r.HandleFunc("/callback", callback.CallbackHandler) +``` + +### 3. Specify the callback on Auth0 Dashboard + +@@includes.callbackRegularWebapp@@ + +In this case, the callbackURL should look something like: + +```` +http://yourUrl/callback +``` +### 4. Triggering login manually or integrating the Auth0Lock + +@@lockSDK@@ + +> **Note:** Please note that the `callbackURL` specified in the `Auth0Lock` constructor **must match** the one specified in the previous step + +### 5. Accessing user information + +You can access the user information via the `profile` you stored in the session on step 2 + +````go +func UserHandler(w http.ResponseWriter, r *http.Request) { + + session, _ := app.GlobalSessions.SessionStart(w, r) + defer session.SessionRelease(w) + + // Getting the profile from the session + profile := session.Get("profile") + + templates.RenderTemplate(w, "user", profile) +} + +``` + +````html +
      + +

      Welcome {{.nickname}}

      +
      +``` + +[Click here](@@base_url@@/user-profile) to check all the information that the userinfo hash has. + +### 6. You're done! + +You have configured your GoLang Webapp to use Auth0. Congrats, you're awesome! + +### Optional steps + +#### Checking if the user is authenticated +We can use [Negroni](https://github.com/codegangsta/negroni) to create a Middleware that will check if the user is Authenticated or not. + +First, we need to install it via `go get`: + +````bash +go get github.com/codegangsta/negroni +``` + +Then, we should create a middleware that will check if the `profile` is in the session: + +````go +package middlewares + +import ( + "net/http" +) + +func IsAuthenticated(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + + session, _ := app.GlobalSessions.SessionStart(w, r) + defer session.SessionRelease(w) + if session.Get("profile") == nil { + http.Redirect(w, r, "/", http.StatusMovedPermanently) + } else { + next(w, r) + } +} +``` + +Finally, we can use Negroni to set up this middleware for any route that needs authentication: + + +````go +r.Handle("/user", negroni.New( + negroni.HandlerFunc(middlewares.IsAuthenticated), + negroni.Wrap(http.HandlerFunc(user.UserHandler)), +)) +``` diff --git a/docs/server-platforms/java.md b/docs/server-platforms/java.md new file mode 100644 index 00000000..6181d14f --- /dev/null +++ b/docs/server-platforms/java.md @@ -0,0 +1,150 @@ +--- +lodash: true +--- + +## Java Webapp Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing Java WebApp to use it with Auth0.** + +### 1. Add Auth0 dependencies + +Add the following dependencies to your `pom.xml` and run `mvn install`. + +````xml + + javax.servlet + javax.servlet-api + 3.0.1 + provided + + + com.auth0 + auth0-servlet + 2.0 + +``` + +### 2. Configure it + +We need to configure `auth0-servlet` to use our Auth0 credentials. For that, just modify the `web.xml` + +````xml + + auth0.client_id + @@account.clientId@@ + + + + auth0.client_secret + @@account.clientSecret@@ + + + + auth0.domain + @@account.namespace@@ + +``` + +### 3. Add Auth0 callback handler + +We need to add the handler for the Auth0 callback so that we can authenticate the user and get his information. For that, we'll use the `Servlet` provided by the SDK. We have to configure it on the `web.xml` + +````js + + RedirectCallback + com.auth0.Auth0ServletCallback + + auth0.redirect_on_success + / + + + auth0.redirect_on_error + /login + + + + RedirectCallback + /callback + +``` + +@@includes.callbackRegularWebapp@@ + +In this case, the callbackURL should look something like: + +```` +http://yourUrl/callback +``` + +### 4. Triggering login manually or integrating the Auth0Lock + +@@lockSDK@@ + +> **Warning:** Auth0 Java requires that you specify the `state` parameter in Auth0 Widget or [Auth0 Lock](https://github.com/auth0/lock/wiki/Auth0Lock-customization#authparams-object). The Login servlet must propagate [the nonce](https://github.com/auth0/auth0-java/blob/2836d13c0a766e3a2fd28fc95bb582fa79e57c52/examples/java-regular-webapp/src/main/java/com/auth0/example/LoginServlet.java#L22) and pass it [to the JSP page](https://github.com/auth0/auth0-java/blob/master/examples/java-regular-webapp/src/main/webapp/login.jsp#L49). For an example of this, check the seed project above. + +> **Note:** Please note that the `callbackURL` specified in the `Auth0Lock` constructor **must match** the one specified in the previous step + +### 5. Accessing user information + +You can access the user information from `Auth0User` by calling `Auth0User.get(request)` or you can get the information directly from the Session variable `user` + +````java +@Override +protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException +{ + resp.setContentType("text/html"); + resp.setStatus(HttpServletResponse.SC_OK); + resp.getWriter().println("\n" + + "\n" + + " \n" + + " \n" + + " Login\n" + + " \n" + + " \n"); + + // This is the same as Request.getSession().getAttribute("user"); + Auth0User user = Auth0User.get(request); + + resp.getWriter().println("

      Welcome

      "); + resp.getWriter().println(""); + resp.getWriter().println("

      Hello " + user.getName() + "!

      "); + resp.getWriter().println(" \n" + + ""); +} +``` + +### 6. You're done! + +You have configured your Java Webapp to use Auth0. Congrats, you're awesome! + +### Optional steps + +#### Checking if the user is authenticated + +You can add a `Filter` to check if the user is authenticated and redirect him to the login page if he's not. For that, we need to configure it in the `web.xml` + +````xml + + AuthFilter + com.auth0.Auth0Filter + + auth0.redirect_on_authentication_error + /login + + + + AuthFilter + /user/* + +``` diff --git a/docs/server-platforms/laravel.md b/docs/server-platforms/laravel.md new file mode 100644 index 00000000..ff2a1628 --- /dev/null +++ b/docs/server-platforms/laravel.md @@ -0,0 +1,121 @@ +# Laravel Auth0 Plugin +This plugin helps you integrate your Laravel WebApp with [Auth0](https://auth0.com/) to achieve Single Sign On with a few simple steps. You can see an example of usage [here](https://github.com/auth0/laravel-auth0-sample) + +## Tutorial + +### 1. Install the plugin and its dependencies + +To install this plugin add the following dependency to your composer.json + + "auth0/laravel-auth0" : "1.0.3" + + +and run `composer update` + +### 2. Enable it in Laravel +Add the following in the list of the services providers, located in `app/config/app.php` + + + 'providers' => array( + // ... + 'Auth0\Login\LoginServiceProvider', + ); + + +Optionally, if you want to use the [facade](http://laravel.com/docs/facades) called `Auth0` you should also add an alias in the same file + + + 'aliases' => array( + // ... + 'Auth0' => 'Auth0\Login\Facade\Auth0' + ); + + +That lets you call the service method like `Auth0::getUserInfo()` or `Auth0::onLogin(function(...))`. + +### 3. Configure it + +To configure the plugin, you need to publish the plugin configuration by executing the following command + + php artisan config:publish auth0/login + +and then modify the config file `app/config/packages/auth0/login/config.php` using your Auth0 account information, as following. + + return array( + 'domain' => '@@account.namespace@@', + 'client_id' => '@@account.clientId@@', + 'client_secret' => '@@account.clientSecret@@', + 'redirect_uri' => '@@account.callback@@' + ); + + + +### 4. Setup the callback action + +The plugin works with the [Laravel security system](http://laravel.com/docs/security), but instead of using the `Auth::attempt` in a controller that handles a login form submit, you have to hookup the callback uri. + +In other words, you need to select a uri (for example /auth0/callback), configure it in your Application Settings and add it as a route in Laravel. + + + Route::get('/auth0/callback', 'Auth0\Login\Auth0Controller@callback'); + + +### 5. Triggering login manually or integrating the Auth0Lock + +@@lockSDK@@ + + +### 6. Defining a user and a user provider + +The [Laravel Security System](http://laravel.com/docs/security) needs a *User Object* given by a *User Provider*. With this two abstractions, the user entity can have any structure you like and can be stored anywhere. You configure the *User Provider* indirectly, by selecting an auth driver in `app/config/auth.php`. The default driver is Eloquent, which persists the User model in a database using the ORM. + +#### 6.1. Using the auth0 driver + +The plugin comes with an authentication driver called auth0. This driver defines a user structure that wraps the [Normalized User Profile](@@base_url@@/user-profile) defined by Auth0, and it doesn't actually persist the object, it just stores it in the session for future calls. + +This works fine for basic testing or if you don't really need to persist the user. In any point you can call `Auth::check()` to see if there is a user logged in and `Auth::user()` to get the wrapper with the user information. + +#### 6.2. Using other driver + +If you want to persist the user you can use the authentication driver you like. The plugin gives you a hook that is called with the *Normalized User Profile* when the callback is succesful, there you can store the user structure as you want. For example, if we use Eloquent, we can add the following code, to persist the user in the database + + + Auth0::onLogin(function($auth0User) { + // See if the user exists + $user = User::where("auth0id", $auth0User->user_id)->first(); + if ($user === null) { + // If not, create one + $user = new User(); + $user->email = $auth0User->email; + $user->auth0id = $auth0User->user_id; + $user->nickname = $auth0User->nickname; + $user->name = $auth0User->name; + $user->save(); + } + return $user; + }); + + +Note that this hook must return the new user, which must implement the `Illuminate\Auth\UserInterface`. The onLogin function is going to be called just once, when the callback uri is called, then its up to the selected auth driver to get the user from the database. + +### 7. Use it! + +Now you can use Laravel filters as you would normally do to restrict access, for example + + + Route::get('admin', array('before' => 'auth', function() { + // ... + })); + + +Or add a logout action like this + + Route::get('/logout', function() { + Auth::logout(); + return Redirect::home(); + }); + + +Enjoy! + + diff --git a/docs/server-platforms/nancyfx.md b/docs/server-platforms/nancyfx.md new file mode 100644 index 00000000..e1f3d6e2 --- /dev/null +++ b/docs/server-platforms/nancyfx.md @@ -0,0 +1,125 @@ +--- +lodash: true +--- + +## NancyFX Tutorial + + + +**Otherwise, please follow the steps below to configure your existing NancyFX WebApp to use it with Auth0.** + +### 1. Install the needed dependencies + +Install Auth0 NancyFX dependency with `NuGet` + +````bash +PM> Install-Package Auth0.NancyFx.SelfHost +``` + +### 2. Configure Auth0 + +In your Nancy self hosted application add the following to your BootStrapper: + +````cs +protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) +{ + // ... + + Auth0Authentication.Enable(pipelines, new AuthenticationConfig + { + RedirectOnLoginFailed = "login", + CookieName = "_auth0_userid", + UserIdentifier = "userid" + }); + + // ... +} +``` +The `RedirectOnLoginFailed` specifies the view that should be shown to an authenticated user when he tries to access a restricted view. + +The `CookieName` allows you to set the name of the cookie that will be used to save the User information. + +The `UserIdentifier` lets you set an identifier for the user. This are the fields that are available to use right now: + + * `userid` + * `email` + * `nickname` + * `gravatarurl` + +> **Important Hint:** Auth0.Nancy.SelfHost enables `CookieBasedSessions` setting in the background. If you use this setting in your app as well, you should switch it off. + +### 3. Add Auth0 configuration + +You need to configure your Auth0 keys in the `app.config` + +````xml + + + + + + + +``` + +### 4. Block all unauthenticated requests + +After you enabled the `Auth0Authentication` you are able to block all unauthenticated requests with the following code. + +````cs +public class SecurePage : NancyModule +{ + public SecurePage() + { + this.RequiresAuthentication(); //<- This is a new implemetation of default extension + Get["/securepage"] = o => View["securepage"]; + } +} +``` + +### 5. Add Auth0 callback handler + +We need to add the handler for the Auth0 callback so that we can authenticate the user and get his information. We also need to add an endpoint to let users Login and Logout + +````cs +public class Authentication : NancyModule +{ + public Authentication() + { + Get["/login"] = o => + { + if (this.SessionIsAuthenticated()) + return Response.AsRedirect("securepage"); + + return View["login"]; + }; + + Get["/login-callback"] = o => this + .AuthenticateThisSession() + .ThenRedirectTo("securepage"); + + Get["/logout"] = o => this + .RemoveAuthenticationFromThisSession() + .ThenRedirectTo("index"); + } +} +``` + +### 6. Triggering login manually or integrating the Auth0Lock + +@@lockSDK@@ + +> **Note:** Please note that the `callbackURL` specified in the `Auth0Lock` constructor **must match** the one specified in the previous step + +### 7. You're done! + +You have configured your NancyFX Webapp to use Auth0. Congrats, you're awesome! + + + diff --git a/docs/server-platforms/nodejs.md b/docs/server-platforms/nodejs.md new file mode 100644 index 00000000..da5905fb --- /dev/null +++ b/docs/server-platforms/nodejs.md @@ -0,0 +1,163 @@ +--- +lodash: true +--- + +## NodeJS Webapp Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing NodeJS WebApp to use it with Auth0.** + +### 1. Add Passport dependencies + +Just run the following code to install the dependencies and add them to your `package.json` + +````js +npm install passport passport-auth0 --save +``` + +### 2. Configure passport-auth0 + +We need to configure Passport to use Auth0 strategy. + +````js +var passport = require('passport'); +var Auth0Strategy = require('passport-auth0'); + +var strategy = new Auth0Strategy({ + domain: '@@account.namespace@@', + clientID: '@@account.clientId@@', + clientSecret: '@@account.clientSecret@@', + callbackURL: '/callback' + }, function(accessToken, refreshToken, extraParams, profile, done) { + // accessToken is the token to call Auth0 API (not needed in the most cases) + // extraParams.id_token has the JSON Web Token + // profile has all the information from the user + return done(null, profile); + }); + +passport.use(strategy); + +// This is not a best practice, but we want to keep things simple for now +passport.serializeUser(function(user, done) { + done(null, user); +}); + +passport.deserializeUser(function(user, done) { + done(null, user); +}); + +module.exports = strategy; +``` + +### 3. Add needed requires & initialize passport configuration + +In the startup file (e.g. _server.js_ or _app.js_) add: + +````js +var passport = require('passport'); + +// This is the file we created in step 2. +// This will configure Passport to use Auth0 +var strategy = require('./setup-passport'); + +// Session and cookies middlewares to keep user logged in +var cookieParser = require('cookie-parser'); +var session = require('express-session'); +``` + +### 4. Configure the middlewares + +Now, just add the following middlewares to your app: + +````js +app.use(cookieParser()); +app.use(session({ secret: 'shhhhhhhhh' })); +... +app.use(passport.initialize()); +app.use(passport.session()); +... +``` + +### 5. Add Auth0 callback handler + +We need to add the handler for the Auth0 callback so that we can authenticate the user and get his information. + +````js +// Auth0 callback handler +app.get('/callback', + passport.authenticate('auth0', { failureRedirect: '/url-if-something-fails' }), + function(req, res) { + if (!req.user) { + throw new Error('user null'); + } + res.redirect("/user"); + }); +``` + +@@includes.callbackRegularWebapp@@ + +In this case, the callbackURL should look something like: + +```` +http://yourUrl/callback +``` + +### 6. Triggering login manually or integrating the Auth0Lock + +@@lockSDK@@ + +> **Note:** Please note that the `callbackURL` specified in the `Auth0Lock` constructor **must match** the one specified in the previous step + +### 7. Accessing user information + +You can access the user information via the `user` field in the `request` + +````js +app.get('/user', function (req, res) { + res.render('user', { + user: req.user + }); +}); +``` + +### 8. You're done! + +You have configured your NodeJS Webapp to use Auth0. Congrats, you're awesome! + +### Optional steps + +#### Checking if the user is authenticated + +You can add the following middleware to check if the user is authenticated and redirect him to the login page if he's not: + +````js +// requiresLogin.js +module.exports = function(req, res, next) { + if (!req.isAuthenticated()) { + return res.redirect('/'); + } + next(); +} +``` +````js +// user.js +var requiresLogin = require('requiresLogin'); + +app.get('/user', + requiresLogin, + function (req, res) { + res.render('user', { + user: req.user + }); + }); +``` diff --git a/docs/server-platforms/php.md b/docs/server-platforms/php.md new file mode 100644 index 00000000..5a10914d --- /dev/null +++ b/docs/server-platforms/php.md @@ -0,0 +1,120 @@ +--- +lodash: true +--- + +## PHP Webapp Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing PHP WebApp to use it with Auth0.** + +### 1. Add Needed dependencies dependencies + +Add the following dependencies to your `composer.json` and run `composer update` + +````json +"require": { + "auth0/auth0-php": "0.6.*", + "adoy/oauth2": "dev-master", + "vlucas/phpdotenv": "dev-master" +} +``` + +> This sample uses **[Composer](https://getcomposer.org/doc/00-intro.md)**, a tool for dependency management in PHP. It allows you to declare the dependent libraries your project needs and it will install them in your project for you. + +### 2. Configure Auth0 PHP Plugin + +````php +use Auth0SDK\Auth0; + +$auth0 = new Auth0(array( + 'domain' => '@@account.namespace@@', + 'client_id' => '@@account.clientId@@', + 'client_secret' => '@@account.clientSecret@@', + 'redirect_uri' => '@@account.callback@@' +)); +``` + +### 3. Add Auth0 callback handler + +Now, we can call `$auth0->getUserInfo()` to retrieve the user information. If we call it from the page that will handle the callback, then it'll use the `code` provided by Auth0 to get the information after the successful login. + +````php +// callback.php + +... +$auth0->getUserInfo(); + +if (!$userInfo) { + // We have no user info + // redirect to Login +} else { + // User is authenticated + // Say hello to $userInfo['name'] + // print logout button +} +``` + +Once the user info is fetched, it'll be stored in the session. Therefore, from this moment on, each time you call `getUserInfo()` it will retrieve the information from the Session. + +@@includes.callbackRegularWebapp@@ + +In this case, the callbackURL should look something like: + +```` +http://yourUrl/callback.php +``` + +### 4. Triggering login manually or integrating the Auth0Lock + +@@lockSDK@@ + +> **Note:** Please note that the `callbackURL` specified in the `Auth0Lock` constructor **must match** the one specified in the previous step + +### 5. Accessing user information + +You can access the user information via the `getUserInfo` method from Auth0 + +````php +getUserInfo(); +?> + + +
      + + +``` + +You can [click here](@@base_url@@/user-profile) to find out all of the available properties from the user's profile. Please note that some of this depend on the social provider being used. + +### 6. You are done! + +You have configured your PHP Webapp to use Auth0. Congrats, you're awesome! + +### Optional steps + +#### Configure session data + +By default, the SDK will store the user information in the PHP Session and it will discard the access token and the id token. If you like to persist them as well, you can pass 'persist_access_token' => true and 'persist_id_token' => true to the SDK configuration in step 2. You can also disable session all together by passing 'store' => false. + +If you want to change PHP Session and use Laravel, Zend, Symfony or other abstraction to the session, you can create a class that implements get, set, delete and pass it to the SDK as following. + +````php +$laravelStore = new MyLaravelStore(); +$auth0 = new Auth0(array( + // ... + 'store' => $laravelStore, + // ... +)); +``` diff --git a/docs/server-platforms/python.md b/docs/server-platforms/python.md new file mode 100644 index 00000000..c09e6e12 --- /dev/null +++ b/docs/server-platforms/python.md @@ -0,0 +1,133 @@ +--- +lodash: true +--- + +## Python Webapp Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing Python WebApp to use it with Auth0.** + +### 1. Add dependencies + +Add the following dependencies to your `requirements.txt` and run `pip install -r requirements.txt` + +````js +flask +requests +``` + +This example uses `flask` but it could work with any server + +### 2. Add the Auth0 Callback Handler + +You'll need to create a callback handler that Auth0 will call once it redirects to your app. For that, you can do the following: + +````python +import os +import json + +import requests +from flask import Flask, request, jsonify, session, redirect, render_template, send_from_directory + +# Here we're using the /callback route. +@app.route('/callback') +def callback_handling(): + env = os.environ + code = request.args.get('code') + + json_header = {'content-type': 'application/json'} + + token_url = "https://{domain}/oauth/token".format(domain='@@account.namespace@@') + + token_payload = { + 'client_id': '@@account.clientId@@', + 'client_secret': '@@account.clientSecret@@', + 'redirect_uri': '@@account.callback@@', + 'code': code, + 'grant_type': 'authorization_code' + } + + token_info = requests.post(token_url, data=json.dumps(token_payload), headers = json_header).json() + + user_url = "https://{domain}/userinfo?access_token={access_token}" \ + .format(domain='@@account.namespace@@', access_token=token_info['access_token']) + + user_info = requests.get(user_url).json() + + # We're saving all user information into the session + session['profile'] = user_info + + # Redirect to the User logged in page that you want here + # In our case it's /dashboard + return redirect('/dashboard') +``` + +### 3. Specify the callback on Auth0 Dashboard + +@@includes.callbackRegularWebapp@@ + +In this case, the callbackURL should look something like: + +```` +http://yourUrl/callback +``` +### 4. Triggering login manually or integrating the Auth0Lock + +@@lockSDK@@ + +> **Note:** Please note that the `callbackURL` specified in the `Auth0Lock` constructor **must match** the one specified in the previous step + +### 5. Accessing user information + +You can access the user information via the `profile` you stored in the session on step 2 + +````python +@app.route("/dashboard") +@requires_auth +def dashboard(): + return render_template('dashboard.html', user=session['profile']) + +``` + +````html +
      + +

      Welcome {{user['nickname']}}

      +
      +``` + +[Click here](@@base_url@@/user-profile) to check all the information that the userinfo hash has. + +### 6. You're done! + +You have configured your Python Webapp to use Auth0. Congrats, you're awesome! + +### Optional steps + +#### Checking if the user is authenticated + +You can add the following annotation to your `Flask` app to check if the user is authenticated + +````python +def requires_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + if not session.has_key('profile'): + # Redirect to Login page here + return redirect('/') + return f(*args, **kwargs) + + return decorated +``` + +We've actually used the annotation on step 5. diff --git a/docs/server-platforms/rails.md b/docs/server-platforms/rails.md new file mode 100644 index 00000000..3915da57 --- /dev/null +++ b/docs/server-platforms/rails.md @@ -0,0 +1,217 @@ +--- +lodash: true +--- + +## Ruby On Rails Webapp Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing Ruby On Rails WebApp to use it with Auth0.** + +### 1. Add dependencies + +Add the following dependencies to your `Gemfile` and run `bundle install` + +````js +gem 'omniauth', '~> 1.2' +gem 'omniauth-auth0', '~> 1.1' +``` + +### 2. Initialize Omniauth Auth0 + +Create a file named `auth0.rb` under `config/initializers` with the following content: + +````ruby +Rails.application.config.middleware.use OmniAuth::Builder do + provider( + :auth0, + '@@account.clientId@@', + '@@account.clientSecret@@', + '@@account.namespace@@', + callback_path: "/auth/auth0/callback" + ) +end +``` + +### 3. Add the Auth0 callback handler + +Use the following command to create the controller that will handle Auth0 callback: + +````bash +rails generate controller auth0 callback failure --skip-template-engine --skip-assets +``` + +Now, go to the newly created controller and add the code to handle the success and failure of the callback. + +````ruby +class Auth0Controller < ApplicationController + def callback + # This stores all the user information that came from Auth0 + # and the IdP + session[:userinfo] = request.env['omniauth.auth'] + + # Redirect to the URL you want after successfull auth + redirect_to '/dashboard' + end + + def failure + # show a failure page or redirect to an error page + @error_msg = request.params['message'] + end +end +``` + +Now, replace the generated routes on `routes.rb` with the following ones: + +````ruby +get "/auth/auth0/callback" => "auth0#callback" +get "/auth/failure" => "auth0#failure" +``` + +### 4. Specify the callback on Auth0 Dashboard + +@@includes.callbackRegularWebapp@@ + +In this case, the callbackURL should look something like: + +```` +http://yourUrl/auth/auth0/callback +``` +### 5. Triggering login manually or integrating the Auth0Lock + +@@lockSDK@@ + +> **Note:** Please note that the `callbackURL` specified in the `Auth0Lock` constructor **must match** the one specified in the previous step + +### 6. Accessing user information + +You can access the user information via the `userinfo` you stored in the session on step 3 + +````ruby +class DashboardController < SecuredController + def show + @user = session[:userinfo] + end +end +``` + +````html +
      + " %>"/> +

      Welcome <%= "\<%= @user[:info][:name] %\>" %>

      +
      +``` + +[Click here](https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema) to check all the information that the userinfo hash has. + +### 7. You're done! + +You have configured your Ruby on Rails Webapp to use Auth0. Congrats, you're awesome! + +### Optional steps + +#### Checking if the user is authenticated + +You can add the following parent controller to all pages that need the user to be authenticated: + +````ruby +class SecuredController < ApplicationController + + before_action :logged_in_using_omniauth? + + private + + def logged_in_using_omniauth? + unless session[:userinfo].present? + # Redirect to page that has the login here + redirect_to '/' + end + end + +end +``` + +### Optional steps +#### Getting the error description on Failure + +In case of failure, you may want to get the description of the error. For that, in your `config/production.rb` add the following: + +````ruby +OmniAuth.config.on_failure = Proc.new { |env| + message_key = env['omniauth.error.type'] + error_description = Rack::Utils.escape(env['omniauth.error'].error_reason) + new_path = "#{env['SCRIPT_NAME']}#{OmniAuth.config.path_prefix}/failure?message=#{message_key}&error_description=#{error_description}" + Rack::Response.new(['302 Moved'], 302, 'Location' => new_path).finish +} +``` + +### Troubleshooting + +#### Troubleshooting ActionDispatch::Cookies::CookieOverflow issue + +If you are getting this error it means that you are using Cookie sessions and since you are storing the whole profile it overflows the max-size of 4K. + +You can change to use In-Memory store for development as follows. + +1. Go to `/config/initializers/session_store.rb` and add the following: + +````ruby +Rails.application.config.session_store :cache_store +``` +2. Go to `/config/enviroments/development.rb` and add the following + +````ruby +config.cachestore = :memorystore +``` + +For production, we recommend using another memory store like MemCached or something similar + +#### Troubleshooting SSL issues + +It seems that under some configurations Ruby can't find certification authority certificates (CA Certs). + +Download CURL's CA certs bundle to the project directory: + +````bash +$ curl -o lib/ca-bundle.crt http://curl.haxx.se/ca/ca-bundle.crt +``` + +Then add this initializer `config/initializers/fix_ssl.rb`: + +````ruby +require 'open-uri' +require 'net/https' + +module Net + class HTTP + alias_method :original_use_ssl=, :use_ssl= + + def use_ssl=(flag) + path = ( Rails.env == "development") ? "lib/ca-bundle.crt" : "/usr/lib/ssl/certs/ca-certificates.crt" + self.ca_file = Rails.root.join(path).to_s + self.verify_mode = OpenSSL::SSL::VERIFY_PEER + self.original_use_ssl = flag + end + end +end +``` +#### Troubleshooting "failure message=invalid_credentials" + +This issue isn't presented while working on your local (development). After deployment on your staging or production envrinoment and after hitting your callback this issue may appear. + +To fix the above error, add the following at your config/environments/staging.rb or production.rb + +````ruby +OmniAuth.config.full_host = "http://www.example.com" +``` + +Please substitute the above url with your desire one. diff --git a/docs/server-platforms/scala.md b/docs/server-platforms/scala.md new file mode 100644 index 00000000..cbe9647a --- /dev/null +++ b/docs/server-platforms/scala.md @@ -0,0 +1,171 @@ +--- +lodash: true +--- + +## Play 2 Scala Tutorial + + + +**Otherwise, Please follow the steps below to configure your existing Play2 Scala WebApp to use it with Auth0.** + +### 1. Add configuration keys from Auth0 + +Add the following keys to your `application.conf` + +````properties +# Auth0 Information +# ~~~~~~~~~~~~~~~~~ + +auth0.clientSecret="@@account.clientSecret@@" +auth0.clientId="@@account.clientId@@" +auth0.domain="@@account.namespace@@" +auth0.callbackURL="http://localhost:9000/callback" +``` + +### 2. Add Auth0 callback handler + +We need to add the handler for the Auth0 callback so that we can authenticate the user and get his information. + +````scala +// conf/routes +GET /callback controllers.Callback.callback(code: Option[String], state: Option[String]) +``` + +````scala +// controllers/Callback.scala +object Callback extends Controller { + + // callback route + def callback(codeOpt: Option[String] = None, stateOpt: Option[String] = None) = Action.async { + (for { + code <- codeOpt + state <- stateOpt + } yield { + // Get the token + getToken(code).flatMap { case (idToken, accessToken) => + // Get the user + getUser(accessToken).map { user => + // Cache the user and tokens into cache and session respectively + Cache.set(idToken+ "profile", user) + Redirect(routes.User.index()) + .withSession( + "idToken" -> idToken, + "accessToken" -> accessToken + ) + } + + }.recover { + case ex: IllegalStateException => Unauthorized(ex.getMessage) + } + }).getOrElse(Future.successful(BadRequest("No parameters supplied"))) + } + + def getToken(code: String): Future[(String, String)] = { + val tokenResponse = WS.url(String.format("https://%s/oauth/token", "@@account.namespace@@"))(Play.current). + withHeaders(HeaderNames.ACCEPT -> MimeTypes.JSON). + post( + Json.obj( + "client_id" -> "@@account.clientId@@", + "client_secret" -> "@@account.clientSecret@@", + "redirect_uri" -> "http://localhost:9000/callback", + "code" -> code, + "grant_type"-> "authorization_code" + ) + ) + + tokenResponse.flatMap { response => + (for { + idToken <- (response.json \ "id_token").asOpt[String] + accessToken <- (response.json \ "access_token").asOpt[String] + } yield { + Future.successful((idToken, accessToken)) + }).getOrElse(Future.failed[(String, String)](new IllegalStateException("Tokens not sent"))) + } + + } + + def getUser(accessToken: String): Future[JsValue] = { + val config = Auth0Config.get() + val userResponse = WS.url(String.format("https://%s/userinfo", config.domain))(Play.current) + .withQueryString("access_token" -> accessToken) + .get() + + userResponse.flatMap(response => Future.successful(response.json)) + } +} +``` + +@@includes.callbackRegularWebapp@@ + +In this case, the callbackURL should look something like: + +```` +http://yourUrl/callback +``` + +### 3. Triggering login manually or integrating the Auth0Lock + +@@lockSDK@@ + +> **Note:** Please note that the `callbackURL` specified in the `Auth0Lock` constructor **must match** the one specified in the previous step + +### 4. Accessing user information + +You can access the user information from the `cache` + +````scala +// controllers/User.scala +def index = AuthenticatedAction { request => + val idToken = request.session.get("idToken").get + val profile = Cache.getAs[JsValue](idToken + "profile").get + Ok(views.html.user(profile)) +} +``` + +````scala +// views/user.html.scala +@(profile: JsValue) + +@main("Auth0 Play2 Scala Sample","home") { + +

      Welcome @((profile \ "name").as[String])

      +} +``` + +### 5. You're done! + +You have configured your Play2 Scala Webapp to use Auth0. Congrats, you're awesome! + +### Optional steps + +#### Checking if the user is authenticated + +You can add the following `Action` to check if the user is authenticated and redirect him to the login page if he's not: + +````scala +def AuthenticatedAction(f: Request[AnyContent] => Result): Action[AnyContent] = { + Action { request => + (request.session.get("idToken").flatMap { idToken => + Cache.getAs[JsValue](idToken + "profile") + } map { profile => + f(request) + }).orElse { + Some(Redirect(routes.Application.index())) + }.get + } +} + +def index = AuthenticatedAction { request => + val profile = Cache.getAs[JsValue](idToken + "profile").get + Ok(views.html.user(profile)) +} +``` diff --git a/docs/servicestack-tutorial.md b/docs/server-platforms/servicestack.md similarity index 87% rename from docs/servicestack-tutorial.md rename to docs/server-platforms/servicestack.md index c338ad3d..7d16fa1e 100644 --- a/docs/servicestack-tutorial.md +++ b/docs/server-platforms/servicestack.md @@ -4,7 +4,7 @@ title: Auth0 and ServiceStack # Using Auth0 with ServiceStack -We provide a [Nuget package](http://nuget.org/packages/Auth0-ServiceStack-OAuthProvider/) to simplify integration of Auth0 with ServiceStack based applications. At the end of this tutorial you will have a working web site that calls a ServiceStack API with authenticated users. +We provide a [Nuget package](http://nuget.org/packages/Auth0-ServiceStack-OAuthProvider/) to simplify integration of Auth0 with ServiceStack based applications. At the end of this tutorial you will have a working web site that calls a ServiceStack API with authenticated users. ##Before you start @@ -18,12 +18,12 @@ Once the default template unfolds, use NuGet to install the **ServiceStack.Host. Install-Package ServiceStack.Host.Mvc -![](img/install-servicestack-nuget.png) +![](//cdn.auth0.com/docs/img/install-servicestack-nuget.png) Add the following line to your `Global.asax` file (this is required for ServiceStack): ``` -routes.IgnoreRoute("api/{*pathInfo}"); +routes.IgnoreRoute("api/{*pathInfo}"); ``` Add a `HomeController` to return the `default.htm` page. Under the __Controllers__ folder add: @@ -44,9 +44,9 @@ Run this command on the __Package Manager Console__: Install-Package Auth0-ServiceStack-OAuthProvider -This command will add two classes to your project under the __App_Start__ folder: `Auth0Provider` and `Auth0UserSession`. +This command will add two classes to your project under the __App_Start__ folder: `Auth0Provider` and `Auth0UserSession`. -`Auth0Provider` extends ServiceStack's `OAuthProvider` and handles the authentication transaction for you. +`Auth0Provider` extends ServiceStack's `OAuthProvider` and handles the authentication transaction for you. ###3. Enable Authentication and plug in Auth0's Provider @@ -77,7 +77,7 @@ private void ConfigureAuth(Funq.Container container) ### 3. Setting up the callback URL in Auth0
      -

      After authenticating the user on Auth0, we will do a POST to a URL on your web site. For security purposes, you have to register this URL on the Application Settings section on Auth0 Admin app (make sure to change the port).

      +

      After authenticating the user on Auth0, we will do a POST to a URL on your web site. For security purposes, you have to register this URL on the Application Settings section on Auth0 Admin app (make sure to change the port).

      http://localhost:PORT/api/auth/auth0/
      @@ -95,7 +95,7 @@ Open your `web.config` file and change the three Auth0's parameters under ` ``` > Notice we are not doing anything useful with these properties. You can place a breakpoint here and explore the session object. -### 6. Triggering login manually or integrating the Auth0 widget +### 6. Triggering login manually or integrating the Auth0Lock -@@sdk@@ +@@lockSDK@@ -### 7. Add UI code to Login and invoke the `HelloService` +### 7. Add UI code to Login and invoke the `HelloService` Open `default.htm` and add the following statement in the `jQuery.ready` body: ```js // get user info from hello endpoint $.getJSON('/api/hello', function (data) { - $('#userInfo').text(JSON.stringify(data.userInfo, 1, 2)); - }); + $('#userInfo').text(JSON.stringify(data.userInfo, 1, 2)); +}); ``` Add a section to display the `UserInfo`: ```html -
      User Info: +
      User Info:
      Not logged in
      ``` diff --git a/docs/server-platforms/symfony.md b/docs/server-platforms/symfony.md new file mode 100644 index 00000000..5516b374 --- /dev/null +++ b/docs/server-platforms/symfony.md @@ -0,0 +1,112 @@ +--- +lodash: true +--- + +# Symfony Tutorial + +If you have used [Symfony](http://symfony.com) before, you are probably already familiar with the [HWIOAuth Bundle](https://github.com/hwi/HWIOAuthBundle). We'll be using it to integrate a Symfony WebApp with [Auth0](https://auth0.com/) and achieve Single Sign On with a few simple steps. + +## Tutorial + +### 1. Add HWIOAuthBundle to your composer.json + + { + "require": { + "hwi/oauth-bundle": "0.4.*@dev" + } + } + +and run `composer update` + + +### 2. Enable the bundle + + // app/AppKernel.php + + public function registerBundles() + { + $bundles = array( + // ... + new HWI\Bundle\OAuthBundle\HWIOAuthBundle(), + ); + } + +### 3. Configure the routes + +Add the following routes at the begining of `app/config/routing.yml` + + hwi_oauth_redirect: + resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml" + prefix: /connect + + hwi_oauth_login: + resource: "@HWIOAuthBundle/Resources/config/routing/login.xml" + prefix: /login + + auth0_login: + pattern: /auth0/callback + + +### 4. Configure Auth0 + +@@includes.callbackRegularWebapp@@ + +In this case, the callbackURL should look something like: + +```` +http://yourUrl/auth0/callback +``` + +### 5. Configure the resource owner + +Add this to your `app/config/config.yml` + + hwi_oauth: + firewall_name: secured_area + resource_owners: + auth0: + type: auth0 + base_url: https://@@account.namespace@@ + client_id: @@account.clientId@@ + client_secret: @@account.clientSecret@@ + +### 6. User provider + +You can create a user provider that implements `OAuthAwareUserProviderInterface` and set it up in step 7, or you +can use one of the predefined services that `HWIOAuthBundle` provides. + +### 7. Configure the oauth firewall + +This is where you set the filters to select which pages are protected (aka, needs login). You can read more on how to configure this at the Symfony [security](http://symfony.com/doc/current/book/security.html) docs. + +This is a basic example that allows anonymous users and then restricts access to the `/demo/hello/` route. It doesn't store the users in a db. + +This file is `app/config/security.yml`: + + security: + providers: + hwi: + id: hwi_oauth.user.provider + + firewalls: + secured_area: + anonymous: ~ + oauth: + resource_owners: + auth0: "/auth0/callback" + login_path: /login + use_forward: false + failure_path: /login + + oauth_user_provider: + service: hwi_oauth.user.provider + + access_control: + - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { path: ^/demo/hello, roles: ROLE_OAUTH_USER } + +Notice that we need to identify the user provided selected in step 6 both in the firewall and in the providers. + +### 8. Triggering login manually or integrating the Auth0Lock + +@@lockSDK@@ diff --git a/docs/sharepoint-apps.md b/docs/sharepoint-apps.md new file mode 100644 index 00000000..11648fcd --- /dev/null +++ b/docs/sharepoint-apps.md @@ -0,0 +1,81 @@ +# Connecting Provider Hosted Apps to SharePoint Online + +Auth0 can help radically simplify the authentication process for SharePoint Apps. Auth0 will negotiate an access token you can the use to call SharePoint APIs. + +You won't need any special libraries. You can use any of the SDKs supported by Auth0. + +##1. Register your app in Auth0 + +Just register a new app in Auth0 as you would normally do: __Applications > NEW__. Pick up any of the SDKs available for detailed instructions. Keep the `client_id` handy, as you will need it in the next step. + +--- + +##2. Create a Package for your app + +You need to obtain a __Client ID__ and a __Client Secret__ for your app. There are many ways of registering your app depending on the expected usage. + +> [This article](http://msdn.microsoft.com/en-us/library/office/jj687469(v=office.15).aspx) explains all different ways of registering your app in SharePoint. This step in the tutorial will use the simplest form: using self-registration in a specific tenant (yours). + +#### Open SharePoint Online + +The URL for the dashboard is `https://{your Office365 tenant}.sharepoint.com/_layouts/15/appregnew.aspx` + +#### Generate a __Client_Id__ and __ClientSecret__: + +![](http://puu.sh/90SvG.png) + +#### Complete the information in the form: + +Since Auth0 is in between your app and the Office 365 infrastructure, you need to use this URL for the app: + +**App Domain**: + + @@account.namespace@@ + +**Redirect URI**: + + https://@@account.namespace@@/login/callback?{SpAppToken}&connection={CONNECTION NAME}&client_id={YOUR APP CLIENT ID}&redirect_uri={YOUR REDIRECT URL} + +* `connection` is just the name you will use in Auth0's connections (e.g. "sharepoint"). +* `client_id` identifies your app in Auth0 (created in steps 1). +* `redirect_uri` is the location in your actual app, where your users will land eventually after all negotiations complete. If you don't specify it, it will always be the app's callback URL defined in Auth0 (it could be localhost) + +#### Package the app and upload to SharePoint: + +Complete the information in your app manifest in Visual Studio: + +![](http://puu.sh/90SEc.png) + +Notice the `Query string` will be exactly like the `Redirect URI` you completed before. Then right-click on the project and select `Publish`: + +![](http://puu.sh/90SUB.png) + +Create a __Publishing Profile__ (you will have to enter the same __Client Id__ & __Client Secret__ obtained in the SharePoint dashboard). + +Click on __Package__ and upload the resulting file to SharePoint. + +--- + +##3. Create the Connection in Auth0 + +The last step in the integration is to add a SharePoint connection in Auth0: + +![](http://puu.sh/8XoVl.png) + +You will need: + +* `Connection Name`. This is an arbitrary name. It has to match with what you entered in step 2. +* `Client Id` & `Client Secret`. Also need to match what you entered in step 2. +* `Test SharePoint Site Url`. This is the SP site URL used to test the connection. (e.g. when pressing the 'Try' button on the dashboard). This is never used at runtime because users will always follow the link to your site from within SharePoint. + +--- + +Users will install your app from the Office Marketplace. When they click on the link, they will be directed to Auth0, which will negotiate the access token for you, and finally to your app. Your app will receive a `User Profile` that will look like this: + +![](http://puu.sh/8Xp6x.png) + +> Notice that the following properties will be included: `cacheKey`, `refresh_token`, `host` and `site`. These will allow you to call back SharePoint APIs (e.g. lists, etc.). + + GET https://yoursite.sharepoint.com/_api/web/lists + Accept: application/json;odata=verbose + Authorization: Bearer {the access_token} \ No newline at end of file diff --git a/docs/sharepoint-clientid.md b/docs/sharepoint-clientid.md new file mode 100644 index 00000000..cf73a75a --- /dev/null +++ b/docs/sharepoint-clientid.md @@ -0,0 +1,4 @@ +# Obtaining a App Id and App Secret for SharePoint + +Sharepoint Online Apps integration is in __experimental mode__. Please contact us if you are interested in this integration: [support@auth0.com](mailto://support@auth0.com) + diff --git a/docs/shopify-clientid.md b/docs/shopify-clientid.md new file mode 100644 index 00000000..7cd2496b --- /dev/null +++ b/docs/shopify-clientid.md @@ -0,0 +1,25 @@ +# Obtaining a API Key and Shared Secret for Shopify + +To configure Shopify OAuth2 connections you will need to register Auth0 with Shopify on their [partner portal](https://app.shopify.com/services/partners/auth/login). + +##1. Create an App on the Partner Portal +Go to the [partner portal](https://app.shopify.com/services/partners), and login with your WordPress credentials. Select __Apps__ and click on __Create app__: + +![](//cdn.auth0.com/docs/img/shopify-devportal-1.png) + +--- + +##2. Complete information about your instance of Auth0 + +Create a new application and complete the form. Use this URL as your callback: + + https://@@account.namespace@@/login/callback + +![](//cdn.auth0.com/docs/img/shopify-devportal-2.png) + +--- + +##4. Get your API Key and Shared Secret + +Once the application is registered, enter your new `API Key` and `Shared Secret` into the connection settings in Auth0. + diff --git a/docs/singlepageapp-tutorial.md b/docs/singlepageapp-tutorial.md new file mode 100644 index 00000000..4408f48b --- /dev/null +++ b/docs/singlepageapp-tutorial.md @@ -0,0 +1,34 @@ +# Using Auth0 in Single Page Apps / HTML5 + +### 1. Setting up the callback URL in Auth0 + +
      +

      After authenticating the user on Auth0, we will redirect to a URL on your web site. For security purposes you must register this URL on the Application Settings section on Auth0 Admin app.

      + +
      http://localhost:PORT/callback
      +
      + +### 2. Triggering login manually or integrating the Auth0Lock + +@@lockSDKWithCallbackOnHash@@ + +### 3. Validate the JsonWebToken on the server + +Auth0 returns a standard [JSON Web Token](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-12) in the browser location hash (http://yourapp#id_token=...). This token should be sent to the backend APIs to be validated in the `Authorization` header. + +This sample code sends the JSON Web Token on each call: + + $.ajaxSetup({ + 'beforeSend': function(xhr) { + if ($.cookie('id_token')) { + xhr.setRequestHeader('Authorization', + 'Bearer ' + $.cookie('id_token')); + } + } + }); + +> Setting the cookie on the client-side using this method requires [jQuery.Cookie](https://github.com/carhartl/jquery-cookie). + +> The settings specified in `ajaxSetup` will affect all calls to $.ajax or Ajax-based derivatives such as $.get(). This can cause undesirable behavior since other callers (for example, plugins) may be expecting the normal default settings. For that reason is recommend against using this API. Instead, set the options explicitly in the call or define a simple plugin to do so ([more details](http://api.jquery.com/jQuery.ajaxSetup/)). + +To validate the token on the server side, we have tutorials for several platforms and languages to get you up and running as quickly as possible. Look at the Web APIs section on the sidebar. diff --git a/docs/siteminder.md b/docs/siteminder.md index 4762dc2e..a56ac976 100644 --- a/docs/siteminder.md +++ b/docs/siteminder.md @@ -14,19 +14,19 @@ Most options are the default values. These are the most important configuration The instructions below will guide you into where these values need to be entered in SiteMinder. -###1. Open the SAML Service Provider Dialog +###1. Open the SAML Service Provider Dialog Provide an appropriate name for this Service Provider. We suggest using: * __Name:__ `@@account.tenant@@` -![](img/siteminder-users.png) +![](//cdn.auth0.com/docs/img/siteminder-users.png) ###2. Defining NameIdentifier You can define many ways of generating a `NameIdentifier` for users authenticating with SiteMinder. Typically you will map this value to one of the user properties in the User Directory as `uid` in the example blow: -![](img/siteminder-nameids.png) +![](//cdn.auth0.com/docs/img/siteminder-nameids.png) ###3. Configure the Service Provider General SAML properties @@ -36,7 +36,7 @@ Use the following values for this configuration screen: * __SAML Version:__ `2.0` * __Skew Time:__ `30 seconds` -![](img/siteminder-general.png) +![](//cdn.auth0.com/docs/img/siteminder-general.png) ###4. Configure the Assertion Consumer Service URL @@ -45,23 +45,23 @@ The __Assertion Consumer Service URL__ is the location where SiteMinder will POS * __Assertion Consumer Service:__ `https://@@account.namespace@@/login/callback` * __HTTP-Post__: `checked` -![](img/siteminder-sso.png) +![](//cdn.auth0.com/docs/img/siteminder-sso.png) ###5. Configure additional user properties to send in the token Add any other properties you wish to share about the authenticated user to this Service Provider. Common values are: `name`, `lastname`, `e-mail address`, etc. This Service Provider will use the `NameIdentifier` defined in [step 2](siteminder#2) as a unique handle of the user. These attributes will be treated as reference information: -![](img/siteminder-attributes.png) +![](//cdn.auth0.com/docs/img/siteminder-attributes.png) ###6. Enter the Single Sign Out URL * __SLO Location URL:__ `https://@@account.namespace@@/logout` -![](img/siteminder-slo.png) +![](//cdn.auth0.com/docs/img/siteminder-slo.png) ###7. Optional Assertion Encryption The Service Provider supports encryption of Assertions. If this option is used, __[download the SP public key certificate]()__ and add it to the __Policy Server Keystore__. -![](img/siteminder-encryption.png) +![](//cdn.auth0.com/docs/img/siteminder-encryption.png) diff --git a/docs/sla.md b/docs/sla.md new file mode 100644 index 00000000..f017c863 --- /dev/null +++ b/docs/sla.md @@ -0,0 +1,38 @@ +# SLA + +At an additional cost, Auth0 offers a __99.9%__ uptime service level agreement guarantee to subscribers enabling it in their subscription. Details on SLA below: + + +#### I. Definitions. +* "Downtime" is period of at least 1 minute in which there is more than a five percent authentication error rate, as measured by a 3rd party service monitoring service such as Pingdom. Downtime is measured based on the backend server side error rate. +* "Monthly Uptime Percentage" means total number of minutes in a calendar month minus the number of minutes of Downtime suffered in a calendar month (other than Permitted Downtime), divided by the total number of minutes in a calendar month. +* “Permitted Downtime” has the meaning set forth in Section III on this Schedule. +* “Non-Permitted Downtime” means any downtime that is not Permitted Downtime. +* “SLC” means service level credits (calculated as set forth below) that Auth0 will credit at the end of the Service term, at no charge to Subscriber. + +#### II. Subscription Services Availability. + +During the Term of the Agreement, the Subscription Services will be operational and available to Subscriber at least 99.9% of the time in minutes in any calendar month (referred to as “Availability”). + +If Auth0 fails to meet its Availability service level due to circumstances not listed within one of the exception in Section III below, and if Subscriber meets its obligations under the Agreement, Subscriber shall be eligible to receive service level credits (“SLCs”) as set forth in Section IV below. + +Auth0 will publish a monthly report describing Service availability on [uptime.auth0.com](http://uptime.auth0.com/749624). + +#### III. Exceptions. + +Subscriber shall receive no SLCs in connection with any unavailability of the Subscription Services caused by any one or more of the following (referred to as “Permitted Downtime”): + +* Circumstances beyond Auth0’s reasonable control, including the Force Majeure Events; +* Scheduled maintenance, such schedule to be published with at least 7 days notice on [status.auth0.com](http://status.auth0.com); +* Defects in software or hardware owned or controlled by Subscriber causing downtime. + +#### IV. Service Level Credits. Auth0 shall apply the following Service Level Credits: + +![](https://docs.google.com/drawings/d/1rCj1NljHTV2wi7kmXdDxHGSS9shpWcp-4zdxCVLlxh8/pub?w=807&h=218) + +Availability is calculated as follows: + +![](https://docs.google.com/drawings/d/1DJtyLsHJrZZySFWwtx0hEs3wTBbiJJ-zMrDjz0oOUFI/pub?w=549&h=100) + +If you require a different SLA or a dedicated instance of Auth0 in your own IT environment contact + diff --git a/docs/soundcloud-clientid.md b/docs/soundcloud-clientid.md new file mode 100644 index 00000000..81be5f05 --- /dev/null +++ b/docs/soundcloud-clientid.md @@ -0,0 +1,21 @@ +# Obtaining a Client ID and Client Secret for SoundCloud + +To configure SoundCloud OAuth2 connections you will need to register Auth0 with SoundCloud on their [developer portal](http://developers.soundcloud.com/). + +##1. Log in into the developer portal +Go to the [developer portal](http://developers.soundcloud.com/), and login with your SoundCloud credentials. Select __Your Apps__ and click on __Register a new app__: + +![](//cdn.auth0.com/docs/img/soundcloud-devportal-1.png) + +--- + +##2. Complete information about your instance of Auth0 + +Create a new application and complete the form. Use this URL as your callback: + + https://@@account.namespace@@/login/callback + +![](//cdn.auth0.com/docs/img/soundcloud-devportal-2.png) + +Enter your new `Client ID` and `Client Secret` into the connection settings in Auth0. + diff --git a/docs/sso/regular-web-apps-sso.md b/docs/sso/regular-web-apps-sso.md new file mode 100644 index 00000000..5655ad7e --- /dev/null +++ b/docs/sso/regular-web-apps-sso.md @@ -0,0 +1,100 @@ +# Server-side SSO (Regular Web Apps) + +Let's say we have three applications + +* App 1: app1.com (single page app) +* App 2: app2.com (single page app) +* App 3: app3.com (regular web app) + +> You can see an example of a Regular Web App configured to use SSO in [this github repository](https://github.com/auth0/auth0-sso-sample/tree/master/app3.com) + +## Case 1: The user is already logged in and clicks on a link that redirects to a specific url app3.com + +The user logs in on app1.com and click on some link should take him to a particular URL on app3.com. In these case, you can create an endpoint on the target application (app3) that will redirect to the URL the user wanted to go after SSO. For example: + +``` +https://app3.com/sso?targetUrl=/foo/bar +``` + +This endpoint would check if the user is already logged in to this app. If it is, then redirect to the target URL. If the user is not logged in on the application, redirect to Auth0 for SSO: + + +``` +handle("/sso") + if (user is already logged in) + redirect to targetUrl + else + redirect to "https://YOURS.auth0.com/authorize?client_id=…&redirect_uri=http://urlTo/callback&response_type=code&state=' + targetUrl +``` + +Here is an example in node.js: + +````js +app.get('/sso', function(req,res, next) { + if (req.isAuthenticated()) { + if (/^http/.test(req.query.targetUrl)) return res.send(400, "url must be relative"); + // Here we'd redirect to req.query.targetUrl like following + // res.redirect(req.query.targetUrl); + // But in this case we'll go to User anyway + res.redirect('/user?targetUrl=' + req.query.targetUrl); + } else { + console.log("Authenticating with Auth0 for SSO"); + passport.authenticate('auth0', { + state: req.query.targetUrl + })(req, res, next); + } +}); +``` + +When the user comes back from Auth0 you should check for the `state` parameter and redirect to the original target URL. + +``` +handle("/callback") + // process login with SDK + if (state) redirect to url on state parameter + else redirect to base logged in URL +``` + +Here is an example in node.js: + +````js +app.get('/callback', + passport.authenticate('auth0'), + function(req, res) { + if (req.query.state) { + res.redirect(req.query.state); + } else { + res.redirect("/user"); + } + }); +``` + +## Case 2: The user is already logged in and goes to app3.com + +The user is logged in on app1.com and opens a new tab and goes to app3.com. You would expect the user to be automatically signed in. To do that, you need to redirect the user to the following URL in a filter or a middleware: + +``` +https://YOURS.auth0.com/authorize?client_id=…&response_type=code&redirect_uri=http://urlTo/callback +``` + +Here is an example in node.js: + +````js +app.get('/', + // This redirects to https://YOURS.auth0.com.... + passport.authenticate('auth0', {}), + function(req, res) { + // Once user is logged in, redirect to the user page + res.redirect('/user'); + }); +``` + +If the user was already logged in before, then Auth0 will automatically redirect back to the application with a new token. If not, it will show the Auth0 Login Page. + +## Case 3: The user never logged in yet + +The user has never logged in in any app. In these case, the filter or middleware mentioned in the previous point checks if the user is authenticated or not and in case he's not, you'd redirect the user to the following url: + +``` +https://YOURS.auth0.com/authorize?client_id=…&response_type=code&redirect_uri=http://urlTo/callback +``` diff --git a/docs/sso/single-page-apps-sso.md b/docs/sso/single-page-apps-sso.md new file mode 100644 index 00000000..3fc97b10 --- /dev/null +++ b/docs/sso/single-page-apps-sso.md @@ -0,0 +1,94 @@ +# Client-side SSO (Single Page Apps) + +Let's say we have three applications + +* App 1: app1.com (single page app) +* App 2: app2.com (single page app) +* App 3: app3.com (regular web app) + +> You can see an example of a SPA configured to use SSO in [this github repository](https://github.com/auth0/auth0-sso-sample/tree/master/app1.com) + +The user logs in on app1.com and tries to access app2.com. Since app2.com is a Single Page App you need to have some code like the following to do SSO. This code should be on every SPA you have (In this case App1 and App2).: + +````html + + + + + + +``` + +> If not using lock, you can use auth0.js for the `getSSOData` and `signin` API + +If the single sign on happens against app3.com (a regular web app), then you have to redirect to `app3.com/sso?targetUrl=/foo/bar`. Read more about this on [Single Sign On with Regular Web Apps](regular-web-apps-sso). + + +## Single Logout + +If the user logged out from app1.com, then we want to clean up the token on app2.com (and app3.com). Read more about [Single Log Out](sso). + +To do that, you have to check every X amount of time whether the SSO session is still alive in Auth0. If it is not, then remove the token from storage for the app. + +````js +setInterval(function() { + // if the token is not in local storage, there is nothing to check (i.e. the user is already logged out) + if (!localStorage.getItem('userToken')) return; + + lock.$auth0.getSSOData(function(err, data) { + // if there is still a session, do nothing + if (err || (data && data.sso)) return; + + // if we get here, it means there is no session on Auth0, + // then remove the token and redirect to #login + localStorage.removeItem('userToken'); + window.location.href = '#login' + + }); +}, 5000) +``` + +> If not using lock, you can use auth0.js for the `getSSOData` and `signin` API diff --git a/docs/sso/single-sign-on.md b/docs/sso/single-sign-on.md new file mode 100644 index 00000000..2a1d3dd3 --- /dev/null +++ b/docs/sso/single-sign-on.md @@ -0,0 +1,25 @@ +# What is SSO? + +SSO (Single Sign On) means that when a user logs in to one application he will be "automatically" signed in to every other application, regardless of the platform, technology and domain. + +For example, Google implements SSO for their products: Gmail, YouTube, Analytics, etc. When you turn on your computer and access Gmail, you login for the first time. Then, if you go to YouTube you won't be prompted for credentials again. + +The way this works is by means of a "central service" (in the case of Google this is https://accounts.google.com). When you login for the first time a cookie gets created on this central service. Then, when you try to access the second application, you get redirected to the central service, but since you already have a cookie, you get redirected to the app directly with a token, which means you're already logged in. + +## How to implement SSO with Auth0? + +For every application in Auth0 that you want to enable SSO, you have to turn on the SSO flag on the application settings on the Auth0 dashoard (or through the API) + +Single Sign On Checkbox + +Then, there are two ways of implementing SSO in Auth0. One involves client-side (JavaScript) and the other is completely server side. + +* [Client-side SSO (Single Page Apps)](single-page-apps-sso) +* [Server-side SSO (Regular Web Apps)](regular-web-apps-sso) + +> You can see an example of SSO with both Single Page Apps and Regular Web Apps in [this github repository](https://github.com/auth0/auth0-sso-sample) + + +# What is Single Log Out? + +Single Logout is the process where you clean up the session of each application the user is logged in. To continue with the Google example, if you logout from Gmail, you get logged out also from YouTube, Google Analytics, etc. diff --git a/docs/ssocircle.md b/docs/ssocircle.md new file mode 100644 index 00000000..9b1bb244 --- /dev/null +++ b/docs/ssocircle.md @@ -0,0 +1,259 @@ +--- +title: ssocircle +layout: doc.nosidebar +--- +# SAML SSO with SSOCircle as an Identity Provider +This tutorial will create a simple example application that uses Auth0 to do SAML Single Sign On (SSO), authenticating users against the __SSOCircle__ Identity Provider. + +There are **6 steps** to this sample + +1. Set up the Auth0 Service Provider. +2. Set up the ssocircle Identity Provider (IDP). +3. Test the connection to the ssocircle IDP. +4. Register a simple HTML application with which to test the end-to-end connection. +5. Create the HTML page for a test application. +6. Test your creation. + +# 1. Set up the Auth0 service provider + +In this step you will configure Auth0 so it knows how to communicate with __SSOCircle__ for single sign on via the SAML protocol. + +**In the Auth0 dashboard:** + +1. Click on **"Connections"** link at left. +2. In the list of options below "Connections", click on **"Enterprise"** +3. In the middle of the screen, click on **"SAMLP Identity Provider"** +4. Click on the blue **"Create New Connection"** button + + +In the "Create SAMLP Identity Provider" connection window, enter the following information into the "Configuration" tab. + +**Connection Name:** You can make up any name, such as "SampleSAMLConnection" + +**Email Domains:** In this example, we will use the Lock Widget, so in the Email Domains field enter the email domain name for the users that will log in via this connection. +For example, if your users have an email domain of 'abc-example.com', you would enter that into this field. You can enter multiple email domains if needed. + +**Sign In URL:** enter this URL: +https://idp.ssocircle.com:443/sso/SSORedirect/metaAlias/ssocircle + +**Sign Out URL:** enter this URL: +https://idp.ssocircle.com:443/sso/IDPSloRedirect/metaAlias/ssocircle + +**Certificate:** Create a file called ssocirclecert.pem and paste in the contents below: + +``` +-----BEGIN CERTIFICATE----- +MIICjDCCAXSgAwIBAgIFAJRvxcMwDQYJKoZIhvcNAQEEBQAwLjELMAkGA1UEBhMCREUxEjAQBgNV +BAoTCVNTT0NpcmNsZTELMAkGA1UEAxMCQ0EwHhcNMTEwNTE3MTk1NzIxWhcNMTYwODE3MTk1NzIx +WjBLMQswCQYDVQQGEwJERTESMBAGA1UEChMJU1NPQ2lyY2xlMQwwCgYDVQQLEwNpZHAxGjAYBgNV +BAMTEWlkcC5zc29jaXJjbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbzDRkudC/ +aC2gMqRVVaLdPJJEwpFB4o71fR5bnNd2ocnnNzJ/W9CoCargzKx+EJ4Nm3vWmX/IZRCFvrvy9C78 +fP1cmt6Sa091K9luaMAyWn7oC8h/YBXH7rB42tdvWLY4Kl9VJy6UCclvasyrfKx+SR4KU6zCsM62 +2Kvp5wW67QIDAQABoxgwFjAUBglghkgBhvhCAQEBAf8EBAMCBHAwDQYJKoZIhvcNAQEEBQADggEB +AJ0heua7mFO3QszdGu1NblGaTDXtf6Txte0zpYIt+8YUcza2SaZXXvCLb9DvGxW1TJWaZpPGpHz5 +tLXJbdYQn7xTAnL4yQOKN6uNqUA/aTVgyyUJkWZt2giwEsWUvG0UBMSPS1tp2pV2c6/olIcbdYU6 +ZecUz6N24sSS7itEBC6nwCVBoHOL8u6MsfxMLDzJIPBI68UZjz3IMKTDUDv6U9DtYmXLc8iMVZBn +cYJn9NgNi3ghl9fYPpHcc6QbXeDUjhdzXXUqG+hB6FabGqdTdkIZwoi4gNpyr3kacKRVWJssDgak +eL2MoDNqJyQ0fXC6Ze3f79CKy/WjeU5FLwDZR0Q= +-----END CERTIFICATE----- +``` + + +When you paste the certificate into your file, line breaks may get replaced with spaces. You need to restore the original line breaks and make sure that the line breaks are **exactly** as shown above and the BEGIN CERTIFICATE and END CERTIFICATE +lines are on their own line and the first and last lines of the file, respectively. + +Click on the red **"UPLOAD CERTIFICATE"** button and select the `.pem` file you just created. + +You can ignore the rest of the fields for now. + +**Save:** Click on the blue **"SAVE"** button at the bottom. + +Here is an example of what the filled-out screen would look like: + +![](https://cdn.auth0.com/docs/img/ssocircle-1.png) + + +After pressing the **"SAVE"** button, A window will appear with a red **"CONTINUE"** button. + +Click on the **"CONTINUE"** button. In the window that appears, near the bottom, there is a line that says, _"You can access the metadata for your connection in Auth0 here:"_. + +Copy the URL below that line into your browser address bar. The picture below shows the screen on which this URL will appear and where to find it: + +![](https://cdn.auth0.com/docs/img/ssocircle-2.png) + +In general, you can access the metadata for a SAML connection in Auth0 here: `https://@@account.namespace@@/samlp/metadata?connection=@@connectionName@@`. + +Once you go to that metadata URL, it will display the metadata for the Auth0 side of the federation. It will look something like the following with your tenant name in place of the 'xxxxx': + +![](https://cdn.auth0.com/docs/img/ssocircle-3.png) + +This metadata needs to be given to the IDP, in this case SSOCircle, so it knows how to interact with Auth0. The steps below will show how to do that. + +Copy the entire contents of the metadata, between and including the start and end `EntityDescriptor` tags: + +```` + " "... + to ... + "" +```` + +You will paste all this into an SSOCircle configuration screen later. + +# 2. Set up the SSOCircle Identity Provider + +In this step you will configure SSOCircle so it knows how to receive and respond to SAML-based authentication requests from Auth0. + +* Go to **[ssocircle.com](http://ssocircle.com)** and register for an account (if you haven't already). + +* Complete the registration and make sure you are logged into SSOCircle. + +* Click on the **“Manage Metadata”** link on the left (toward the bottom of the links) + +* Click on the link **“Add new Service Provider”** + +* Fill out the following fields: + +**FQDN of the ServiceProvider:** Auth0.com + +**Attributes send in assertion:** check the box for ‘EmailAddress’ + +**Insert your metadata information:** paste in the XML metadata that you downloaded after configuring Auth0. E.g. the info that starts with: +` Enterprise -> SAMLP Identity Provider__. + +* Click on the triangular **"Try"** button for the SAML connection you created earlier. This button is to the right of the name of the connection. You can hover your mouse over the button to have the text label appear. + +* Click on the **"Try"** button. You should be redirected from Auth0 to the SSOCircle login page. You may receive a window that says _"Your session has timed out."_ with a link to _"Return to Login page"_ below it. If so, just click on the _"Return to Login page"_ link. + +* Once you are at the **SSOCircle login screen**, login with the credentials you provided when you created the SSOCircle account and press the "Login" button. + +If the SAML configuration works, your browser will be redirected back to an Auth0 page that says __"It works!!!"__. This page will display the contents of the SAML authentication assertion sent by the SSOCircle IDP to Auth0. +This means the connection from Auth0 to the SSOCircle IDP is working. + +If it didn't work, double check the above steps and then consult the **troubleshooting** section at the end of this document. + +> NOTE: the **Try** button only works for users logged in to the Auth0 dashboard. You cannot send this to an anonymous user to have them try it. + +Here is a sample of the "It Works" screen: + +![](https://cdn.auth0.com/docs/img/ssocircle-5.png) + +# 4. Register a simple HTML application with which to test the end-to-end connection. + +In this step, you will register an application in Auth0 that will use the SAML connection you set up in the above steps. + +* In the **Auth0 dashboard**, click on the **"Apps/APIs"** link at left. + +* Click on the red **"+ NEW APP/API"** button on the right. + +* In the **Name** field, enter a name like "My-HTML-SAML-App". + +* Press the blue **"SAVE"** button. + +* In the **Auth0 dashboard**, click again on the **"Apps/APIs"** link at left + +* Find the row for the application you just created, and click on the **"Settings"** icon to the right of the application name. (the round gear icon) + +* In the **"Allowed Callback URL"** field, enter **http://jwt.io**. +* The list of allowed callback URLs is a list of URL(s) to which users will be redirected after authentication. The URL(s) entered here must match the **"callback URL"** in the HTML code created in the next step. Normally you would enter a URL for your application, but to keep this example simple, users will simply be sent to the Auth0 JWT online tool. + +* Press the blue **"SAVE CHANGES"** button at the bottom of the screen. + +* In the same screen, click on the blue **"Connections"** tab (In the row that says Quick Start, Settings etc. + +* Scroll down to the section near the bottom where it says **"ENTERPRISE"**. + +* Find the row for the SAML connection you created above and click on the on/off toggle at right so that it is green, for "on". That enables the SAML connection for this application. + +![](https://cdn.auth0.com/docs/img/ssocircle-6.png) + +# 5. Create the HTML page for a test application + +In this section you will create a very simple HTML page that invokes the **Auth0 Lock Widget** which will trigger the SAML login sequence. This will enable an end-to-end test of the SAML SSO. + +Create an HTML page and insert the following HTML and javascript code: + + +``` + + + +

      Click on the button to log in

      + + + + + + + +``` + +Make sure you replace `{YOUR-APP-CLIENT-ID}` with the actual value of the app you registered in step 4. + +The client ID for your application can be found in the **Auth0 dashboard** by going to __"Apps/APIs"__ link and clicking on the __"Settings"__ (gear) icon to the right of your application name. + +Save this file in a place where you can access it via a browser. +For this example, we'll call it **"hello-saml.html"**. + +# 6. Test your sample application + +In this step, you will test your sample HTML application that uses the Auth0 SAML connection you set up to perform SSO with SSOCircle. + +* Open the HTML file created above with a browser. You should first see a white page with a login button on it. + +* Click on the **login** button. + +The **Auth0 Lock** widget should appear with one button titled **"saml".** + +If you have other connections turned on for your application, your **Auth0 Lock Widget** may look slightly different. If you are prompted to enter an email address, make sure the email address you enter has the same domain name as the domain(s) you entered in the __Settings__ tab for the application in the Auth0 dashboard. (__Apps/APIs -> Settings__) + +Click on the **"saml"** button or the **ACCESS** button to initiate the SAML sso sequence with ssocircle. + +![](https://cdn.auth0.com/docs/img/ssocircle-7.png) + +* You will be redirected to the SSOCircle IDP to log in. + +Note that whether you are prompted for credentials at this point depends on whether you still have an active session at SSOCircle. + +From the "try me" test you did earlier, you may still have an active session at SSOCircle. If this is the case, you will not be prompted to log in again and will simply be redirected to the callback URL specifed in the HTML file. (Remember that this callback URL must also be in the __Allowed Callback URLs__ in the application's Setting tab in the Auth0 dashboard.) + +If sufficient time has passed, or if you delete your browser cookies before initiating the test, then you will be prompted to login when redirected to ssocircle.com. Log in to SSOCircle using the credentials with which you established your account at SSOCircle. + +![](https://cdn.auth0.com/docs/img/ssocircle-8.png) + +Upon successful authentication, you will be redirected to the callback URL specified in the HTML file (jwt.io). This tool will display the token that your app would receive. + +#7. Troubleshooting. + +This section has a few ideas for things to check if your sample doesn't work. + +Note that if your application doesn't work the first time, you should clear your browser history and ideally cookies each time before you test again. Otherwise, the browser may not be picking up the latest version of your html page. + +When troubleshooting SSO, it is often helpful to capture an HTTP trace of the interaction. There are many tools that will capture the HTTP traffic from your browser for analysis. Search for "HTTP Trace" to find some. Once you have an http trace tool, capture the login sequence from start to finish and analyze the trace to see the sequence of GETs to see how far in the expected sequence you get. You should see a redirect from your original site to the IDP, a post of credentials if you had to log in, and then a redirect back to the callback URL. + +Be sure to check to make sure cookies and javascript are enabled for your browser. + +Check to make sure that the callback URL specified in the HTML file is also listed in the **Allowed Callback URLs** field in the __""Settings""__ tab of the application registered in the Auth0 Dashboard. (In dashboard, Click on __"Apps/APIs"__ link, then on the __"Settings"__ icon to the right of the application name.) + + diff --git a/docs/test-partner-connection.md b/docs/test-partner-connection.md new file mode 100644 index 00000000..42b5aa99 --- /dev/null +++ b/docs/test-partner-connection.md @@ -0,0 +1,46 @@ +# Testing a partner connection + +Testing a connection in Auth0 is very easy: + +1. Click on the __Try__ button on each connection. +2. Login with the identity provider. +3. Wait for the __It Works!__ page that displays the result. + +Auth0 simulates the authentication flow as if it was an application, displaying the __User Profile__ resulting from a successful authentication. + +There's a caveat though: for this to work you have to be logged-in in the dashboard. + +This is often not possible if you are testing a connection that belongs to someone else, and you don't have test credentials with them. This is common when connecting to __Enterprise connections__ such as __SAML IdPs__ or __Active Directory__. + +Having your partners test the new connection is very easy nevertheless: + +##1. Create a Test app + +Register a new application on Auth0: __[Dashboard > Apps/APIs > Create](https://manage.auth0.com/#/applications/create)__. You can give it any name: __Test App__ + +In the settings of the newly created app, configure __Allowed Callback Urls__ to __[http://jwt.io](http://jwt.io)__. + +Click on __SAVE CHANGES__. + + +##2. Send your Partner the link to login + + https://@@account.namespace@@/authorize?response_type=token&scope=openid%20profile&client_id={THE_APP_CLIENT_ID}&redirect_uri=http://jwt.io&connection={THE_CONNECTION_YOU_WANT_TO_TEST} + +Make sure you replace these two parameters: + +* __client_id__: the app client_id created in __Step 1__. +* __connection__: the name of the connection you want to test. + +##3. Test it! + +When your partner follows the link above, she will be redirected to their configured Identity Provider (the __connection__). After successful authentication, she will be sent back to __[http://jwt.io](http://jwt.io)__ where all user properties will be decoded from the token. + +> Notice that the test app is not a _real_ app. __jwt.io__ is just a testing website that is able to decode tokens sent on a URL fragment. + + + + + + + diff --git a/docs/thecity-clientid.md b/docs/thecity-clientid.md new file mode 100644 index 00000000..13907487 --- /dev/null +++ b/docs/thecity-clientid.md @@ -0,0 +1,31 @@ +# Obtaining a App ID and Secret with The City + +##1. Log in into The City portal + +Log in into your The City portal, and select __Admin__: + +![](//cdn.auth0.com/docs/img/thecity-register-1.png) + +--- + +##2. Create new Plugin + +Select __API > Plugin > Create plugin__: + +![](//cdn.auth0.com/docs/img/thecity-register-2.png) + +Complete the form using this callback URL: + +![](//cdn.auth0.com/docs/img/thecity-register-3.png) + + https://@@account.namespace@@/login/callback + +Press __Create__ + +--- + +##3. Get your App ID & Secret + +Once the application is created, enter your new `App ID` and `Secret` into the connection settings in Auth0. + +![](//cdn.auth0.com/docs/img/thecity-register-4.png) diff --git a/docs/twitter-clientid.md b/docs/twitter-clientid.md index 0102ba1d..5338ed73 100644 --- a/docs/twitter-clientid.md +++ b/docs/twitter-clientid.md @@ -5,9 +5,9 @@ To configure a Twitter connection you will need to register Auth0 with Twitter. ##1. Log in into Twitter's Developers site Log in into [Twitter's Developer site](https://dev.twitter.com), select __My Applications__ and click on the __Create an New Appliction__ button: -![](img/twitter-api-1.png) +![](//cdn.auth0.com/docs/img/twitter-api-1.png) -![](img/twitter-api-2.png) +![](//cdn.auth0.com/docs/img/twitter-api-2.png) --- @@ -17,14 +17,16 @@ Your callback URL into Auth0 is: https://@@account.namespace@@/login/callback -![](img/twitter-api-3.png) +![](//cdn.auth0.com/docs/img/twitter-api-3.png) + +Once the application is created, go to Settings tab and make sure to check the __"Allow this application to be used to Sign in with Twitter"__ option. --- -##4. Get your Consumer Key and Consumer Secret +##3. Get your Consumer Key and Consumer Secret -Once the application is registered, enter your new `Consumer Key` and `Consumer Secret` into the connection settings in Auth0. +Go back to Details tab, and enter your new `Consumer Key` and `Consumer Secret` into the connection settings in Auth0. -![](img/twitter-api-4.png) +![](//cdn.auth0.com/docs/img/twitter-api-4.png) -![](img/twitter-api-5.png) \ No newline at end of file +![](//cdn.auth0.com/docs/img/twitter-api-5.png) \ No newline at end of file diff --git a/docs/user-profile.md b/docs/user-profile.md index deb10786..3525a8f2 100644 --- a/docs/user-profile.md +++ b/docs/user-profile.md @@ -1,12 +1,13 @@ # Auth0 Normalized User Profile -Not all the identity providers will supply the same amount of information about the user. They might even choose different properties to convey the same meaning (e.g. `last_name` vs `family_name`). Auth0 normalizes the profile of the user regardless of the identity provider the user is authenticating with. This of course greatly simplifies the development experience, as you just need to be concerend with one schema. +Not all the identity providers will supply the same amount of information about the user. They might even choose different properties to convey the same meaning (e.g. `last_name` vs `family_name`). Auth0 normalizes the profile of the user regardless of the identity provider the user is authenticating with. This of course greatly simplifies the development experience, as you just need to be concerned with one schema. These are the attributes that Auth0 will provide: * `user_id`: A unique identifier of the user per identity provider, same for all apps (e.g.: google-oauth2|103547991597142817347). **ALWAYS GENERATED** * `name`: The full name of the user (e.g.: John Foo). **ALWAYS GENERATED** -* `email`: Email of the user. **ALWAYS GENERATED** +* `email`: Email of the user. (if available from provider. E.g. twitter won't give you one. If using facebook or windows live, you will have to ask for extra user consent). +* `email_verified`: Whether the email of the user has been verified. When using Database Connections an email is sent to the user on signup with a link that sets. When using Enterprise or Social Connections this flag comes from the identity provider. If it is not provided, then an email will be sent to the user to verify. Email verification can be turned on/off on the Dashboard under the Emails section. * `nickname`: User name (if available, might not be unique across identity providers). **ALWAYS GENERATED** * `picture`: URL pointing to the user picture (if not available, will use [gravatar.com](http://gravatar.com) with the email). **ALWAYS GENERATED** * `given_name`: First name of the user (if available). @@ -15,14 +16,27 @@ These are the attributes that Auth0 will provide: Another piece of information added to the user profile is an array of identities. This is used when the user associates one account with a new one (e.g.: Google and Facebook different accounts, same person). * `access_token`: inside the identities array you will find one record per identity provider the user has associated. If the identity provider is OAuth2, you will find the `access_token` that can be used to call the provider API and obtain more information from the user (e.g: Facebook friends, Google contacts, LinkedIn contacts, etc.) +* `access_token_secret`: **currently only for twitter**. If the identity provider is OAuth 1.0a, an `access_token_secret` property will be present and can be used to call the provider API and obtain more information from the user. -> **NOTE:** Auth0 will pass through the rest of the attributes it couldn't map. +> **NOTE:** Auth0 will pass-through to the app any other properties supplied by the identity provider, that are not mapped to the standard attributes named above. + +## Keeping User Data on your Application + +When outsourcing user authentication there is no more a Users/Passwords table, but you will still want to associate application data to the authenticated user. You can have a **Users table**, that would have a copy of each user coming from Auth0, without passwords of course. Every time a users logs in, you would search that user if it does not exist, insert it, if it does, update all the fields, essentially keeping a local copy of the user data. Another option would be to have the user identifier on each table/collection that has user-associated data. For smaller applications, that would be easier to implement. + +But the next question is, how do you uniquely identify a user coming from Auth0? There are two options: + +1. Using the `user_id` property which is unique per user per identity provider. +2. Using the `email` property. In this case it's very important to turn on Email Verification and also check that `email_verified` is `true`, otherwise you would be open to some edge case where a user might signup using an identity provider that provides email but it doesn't verify it. Also, in some cases like Twitter, email is not provided. + +## Sample User Profiles This is a sample user profile from a user that logged in through **Google**: ``` { "email": "johnfoo@gmail.com", + "email_verified": true, "family_name": "Foo", "gender": "male", "given_name": "John", @@ -48,6 +62,7 @@ This is a sample profile from **Windows LiveID (Microsoft Accounts)**: ``` { "email": "bobdoe@outlook.com", + "email_verified": true, "emails": [ "bobdoe@outlook.com", "bobdoe@outlook.com" @@ -71,7 +86,7 @@ This is a sample profile from **Windows LiveID (Microsoft Accounts)**: } ``` -This is a sample profile from **Office 365 (Windows Azure Active Directory)**: +This is a sample profile from **Office 365 (Microsoft Azure Active Directory)**: ``` { @@ -101,6 +116,7 @@ This is a sample profile from **ADFS (Active Directory Federation Services)**: { "email": "john@fabrikam.com", "family_name": "Fabrikam", + "email_verified": false, "given_name": "John", "identities": [ { diff --git a/docs/versioning.md b/docs/versioning.md new file mode 100644 index 00000000..c3c8ea6d --- /dev/null +++ b/docs/versioning.md @@ -0,0 +1,40 @@ +--- +title: How Auth0 versioning works? +--- + +# Versioning + +We believe versioning is a crucial part of our offering. By providing a consistent versioning scheme for our products we are able to help our users manage and predict how our changes will impact usage. + +## Semantic versioning + +> This scheme is used in [Auth0 Lock](https://github.com/auth0/lock), [Auth0 AD Connector](https://github.com/auth0/ad-ldap-connector), [Auth0.js](https://github.com/auth0/auth0.js) and SDKs. + +[Semantic versioning](http://semver.org) (also known as semver) is a versioning strategy whose main feature is making breaking changes discoverable. A version is composed of 3 numbers separated by dots: `{major}.{minor}.{patch}`. For instance, `2.12.5`, `0.1.0` and `10.5.35` are valid semver numbers. + +- The first number represents a **major change**: **the library API has changed in a non-backwards compatible way**. When the major part of a version is bumped, the public API of that library has changed. For example, code and functionality previously marked as deprecated is removed from the code base. +- The second number represents a **minor change**: **the library API has new functionality added or marked as deprecated while keeping backward compatibility**. The new minor version is expected to be safe for use and we encourage customers to update. However, as it is impossible to know every way customers use a component, there is always a chance that changes might have impact on the current usage of the component. Therefore, we recommend verifying and testing before performing an update. +- The third number, represents **a patch change**: **A bug has been fixed and should not have any impact on the user facing API**. This should be safe to update but testing is always encouraged. + +More information about this versioning scheme can be found at [semver](http://semver.org) + +### Production Usage + +Auth0 provides links to our [Content Delivery Network (CDN)](http://en.wikipedia.org/wiki/Content_delivery_network) where we serve some of our libraries. The way you reference a component in your code will impact whether and when you automatically pick up changes. For instance, Auth0 Lock can be found at the following URLs: + +```js + + + + + + + + +``` + +If you link to the major release (the first script) and we release a new minor, you will get the update as soon as it is released. We encourage this practice for development environments and experimenting with Auth0. + +**When Auth0 components are deployed to production, we encourage our users to anchor to a full version and thoroughly test using that version**. Although adding new features in a backwards compatible way shouldn’t break your code, the interactions with the component outcome could be hard to predict. Even trivial bug fixes may introduce changes in the assumptions you were making about the component which may no longer be true. + +> Note about git: Each Auth0 open source component that follows semver will have a tag matching a released version in its git repository. As some of the projects use [npm](https://npmjs.com) tooling to release versions the tags will have a “v” letter as a prefix. For instance, version `5.2.3` tag will be `v5.2.3`. diff --git a/docs/vkontakte-clientid.md b/docs/vkontakte-clientid.md index dbf21566..e3a70504 100644 --- a/docs/vkontakte-clientid.md +++ b/docs/vkontakte-clientid.md @@ -5,11 +5,11 @@ To configure an vKontakte connection you will need to register your Auth0 instan ##1. Create a new Application in vKontakte: Log in into vKontakte and [create a new app](http://vk.com/editapp?act=create): -![](img/vkontakte-create-app.png) +![](//cdn.auth0.com/docs/img/vkontakte-create-app.png) You will be required to confirm the request with an SMS message: -![](img/vkontakte-validate-create-app.png) +![](//cdn.auth0.com/docs/img/vkontakte-validate-create-app.png) --- @@ -17,7 +17,7 @@ You will be required to confirm the request with an SMS message: Complete the form: -![](img/vkontakte-register-app.png) +![](//cdn.auth0.com/docs/img/vkontakte-register-app.png) The callback address for your app should be: @@ -29,5 +29,5 @@ The callback address for your app should be: Once the application is registered, enter your new `Application ID` and `Secure Key` into the connection settings in Auth0. -![](img/vkontakte-add-connection.png) +![](//cdn.auth0.com/docs/img/vkontakte-add-connection.png) diff --git a/docs/waad-clientid.md b/docs/waad-clientid.md index 6de71fb4..1dd1834f 100644 --- a/docs/waad-clientid.md +++ b/docs/waad-clientid.md @@ -1,74 +1,77 @@ - -# Obtaining a ClientId and Client Secret for a Windows Azure Active Directory +To allow users to login using a Microsoft Azure Active Directory account you have to register your application through the Microsoft Azure portal. If you don't have a Microsoft Azure account, you can signup for one, free, here . -To allow users to login using a Windows Azure Active Directory account you have to register your application through the Windows Azure portal. If you don't have a Windows Azure account, you can signup for one, free, here . +> NOTE: there is no way to create an application that integrates with Microsoft Azure AD without having **your own** Microsoft Azure AD instance. -> NOTE: there is no way to create an application that integrates with Windows Azure AD without having **your own** Windows Azure AD instance. +## 1. Create a new Microsoft Azure Active Directory instance -## 1. Create a new Windows Azure Active Directory instance +After signing up on Microsoft Azure, click on **Active Directory** item on the Dashboard. -After signing up on Windows Azure, click on **Active Directory** item on the Dashboard. + -![](img/waad-0.png) +Click on **ADD+** at the bottom of the screen: -Click on **CREATE YOUR DIRECTORY** + -![](img/waad-1.png) +Enter a subdomain, e.g.: **@@account.tenant@@** (this could be anything, does not have to match with Auth0 subdomain and it will be used in the next step). Enter also your country and a friendly name for the organization. -Enter the subdomain, e.g.: **@@account.tenant@@** (this could be anything, does not have to match with Auth0 subdomain and it will be used in the next step). Enter also your country and a friendly name for the organization. + -![](img/waad-2.png) +## 2. Create a new Application -## 2. Create a new Integrated Application +Once the Microsoft Azure AD was created, go to **APPLICATIONS** and click on **ADD AN APPLICATION**: -Once the Windows Azure AD was created, go to **INTEGRATED APPS** and click on **ADD AN APP** + -![](img/waad-3.png) +Select "Add an application my organization is developing": -Enter a friendly name for the application and select either **SINGLE SIGN ON** or **SINGLE SIGN ON, READ DIRECTORY DATA**. The permission to **READ DIRECTORY DATA** will allow you to do things like a query of "users in the Windows Azure AD domain". + -![](img/waad-4.png) +Enter a friendly name for the application and select "WEB APPLICATION AND/OR WEB API": -Enter the following: + -* **APP URL**: your application URL (completely arbitrary) +Proceed to the next screen and enter the following: + +* **SIGN-ON URL**: your application URL (completely arbitrary) * **APP ID URI**: https://**@@account.tenant@@**.onmicrosoft.com/yourapp -> NOTE: The APP ID URI is just a logical identifier, not a real URL. It is important to use the value as specified above in APP ID URI. For instance, if the Windows Azure AD you've just created is **myorg.onmicrosoft.com**, here you would enter https://**myorg.onmicrosoft.com**/yourapp. +> NOTE: The APP ID URI is just a logical identifier, not a real URL. It is important to use the value as specified above in APP ID URI. For instance, if the Microsoft Azure AD you've just created is **myorg.onmicrosoft.com**, here you would enter https://**myorg.onmicrosoft.com**/yourapp. -![](img/waad-5.png) + -## 3. Configure the Integrated Application +## 3. Configure the Application -Once the application has been created, you will have to configure a couple of things. Click on **CONFIGURE** to continue. +Once the application has been created, you will have to configure a couple of things. Click **CONFIGURE** to continue. On this screen you can customize the logo and the application URL that you entered before if needed. -![](img/waad-6.png) +Enter the following values on **KEYS** and **REPLY URL**, and click **Save**. -First, make sure to turn on **EXTERNAL ACCESS**. You can also customize the logo here and the application URL that you entered before. +* **KEYS**: Select 1 or 2 years (when you save it will show the key) +* **REPLY URL**: https://@@account.namespace@@/login/callback -![](img/waad-7.png) + -Then, enter the following values on **KEYS** and **REPLY URL**, and click **Save**. +The last step: modify permissions so your app can read the directory and click on **SAVE** at the bottom of the screen. -* **KEYS**: Select 1 or 2 years (when you save it will show the key) -* **REPLY URL**: https://@@account.namespace@@/login/callback + -![](img/waad-8.png) +> NOTE: If you want to enable some extended attributes (like `Extended Profile` or `Security Groups`) you need also to enable the following permissions: **Application Permissions: Read directory data**, **Delegated Permissions: Access your organization's directory**. Make sure to copy the value of the secret before leaving this screen. -![](img/waad-9.png) + -## 4. Copy the Client ID and Secret in Auth0 +## 4. Copy the Client ID and Secret Auth0 Finally, copy and paste the Client ID and the Key in Auth0. -![](img/waad-10.png) + + +**Congratulations!** You are now ready to accept Microsoft Azure AD users. + +## Troubleshooting + +* Make sure to use an Incognito/InPrivate window when granting access and use a Global Administrator user. -**Congratulations!** You are now ready to accept Windows Azure AD users. +* If you get _Access cannot be granted to this service because the service listing is not properly configured by the publisher._ try turning on the **Application is Multi Tenant** option in the Windows Azure AD application on the Azure dashboard. diff --git a/docs/wcf-tutorial.md b/docs/wcf-tutorial.md index 7d077e21..1542015f 100644 --- a/docs/wcf-tutorial.md +++ b/docs/wcf-tutorial.md @@ -1,19 +1,19 @@ # Using Auth0 with a WCF Service -This tutorial explains how to consume a WCF service and validating the identity of the caller. +This tutorial explains how to consume a WCF service, validating the identity of the caller. -In Web Services or APIs there are two ways these services can be called +When calling a Web Service (or an API in general) there are two ways users are typically authenticated: * Through a client that has access to a key that can be used to obtain a token. -* Through a client that has access to a token that was obtained somehow. +* Through a client that has access to a token that was obtained through some other method. -Typically, first scenario usually happens on a trusted client (a script, a desktop application). The second scenario is the browser, a mobile native app. +The first scenario usually happens on trusted clients (e.g. a script, a desktop application). The second scenario is more often a browser, or a mobile native app. For this tutorial, we will assume the standard WCF template with a `basicHttpBinding`. -##Making WCF to validate JsonWebTokens generated by Auth0 +##Using Auth0 generated JsonWebTokens with WCF services -The integration consists of adding a `ServiceAuthorizationManager` (which is an extensibility point from WCF). This class intercepts all the calls to a specific service and extracts the HTTP `Authorization` header that contains the JsonWebToken. Then it validates the token using a symmetric or asymmetric key, check that it didn't expire and the audience is correct and finally fills a `ClaimsPrincipal` and set it to the thread principal so it can be accessed later. +The integration consists of adding a `ServiceAuthorizationManager` (which is an extensibility point offered by WCF). This class intercepts all calls to a specific service and extracts the HTTP `Authorization` header that contains the JsonWebToken. Then it validates the token using a symmetric or asymmetric key, checks that it's not expired, and finally verifies that the `audience` is correct. If all these are correct, control is transfered to the user code with a `ClaimsPrincipal` object set for the app to use. ###1. Install Auth0-WCF-Service-JWT NuGet package @@ -23,9 +23,9 @@ Use the NuGet Package Manager (Tools -> Library Package Manager -> Package Manag > This package creates the `ServiceAuthorizationManager` and will add a set of configuration settings. -###2. Filling Web.Config with your Auth0 settings +###2. Completing your app Web.Config with Auth0 settings -The NuGet package also created three empty settings on ``. Replace them with the following settings: +The NuGet package will create three empty settings under the `` section. Replace them with the following values: @@ -33,22 +33,22 @@ The NuGet package also created three empty settings on ``. Replace -And make sure to add the `` +Make sure to add the `` element as well: ###3. Accessing user information -Once the user successfully authenticated to the application, a `ClaimsPrincipal` will be generated which can be accessed through the `User` property or `Thread.CurrentPrincipal` +Once the user is successfully authenticated with the application, a `ClaimsPrincipal` will be generated which can be accessed through the `User` or `Thread.CurrentPrincipal` properties: public class Service1 : IService1 { public string DoWork() { - var claims = (User.Identity as IClaimsIdentity).Claims + var claims = ((ClaimsIdentity)Thread.CurrentPrincipal.Identity).Claims string email = claims.SingleOrDefault(c => c.ClaimType == "email"); - return "Hello from WCF " + Thread.CurrentPrincipal.Identity.Name + "(" + email + ")"; + return "Hello from WCF " + User.Identity.Name + " (" + email + ")"; } } @@ -61,7 +61,7 @@ Install the NuGet package on the client side Extract the `id_token` from the `ClaimsPrincipal` and attach it to the WCF request // get JsonWebToken from logged in user - string token = ClaimsPrincipal.Current.FindFirst(c => c.Type == "id_token").Value; + string token = ClaimsPrincipal.Current.FindFirst("id_token").Value; // attach token to WCF request client.ChannelFactory.Endpoint.Behaviors.Add(new AttachTokenEndpointBehavior(token)); @@ -72,14 +72,14 @@ Extract the `id_token` from the `ClaimsPrincipal` and attach it to the WCF reque > **Note**: the above asumes that the WCF service is protected with the same client secret as the web site. If you want to call a service protected with a different secret you can obtain a delegation token as shown below: // get JsonWebToken from logged in user - string token = ClaimsPrincipal.Current.FindFirst(c => c.Type == "id_token").Value; + string token = ClaimsPrincipal.Current.FindFirst("id_token").Value; // create an Auth0 client to call the /delegation endpoint using the client id and secret of the caller application - var auth0 = new Auth0.Client("@@account.clientSecret@@", "@@account.clientId@@", "@@account.namespace@@"); - var result = auth0.GetDelegationToken(token, "...client id of the target service..."); + var auth0 = new Auth0.Client("...caller client id...", "...caller client secret...", "@@account.namespace@@"); + var result = auth0.GetDelegationToken(token, "@@account.clientClient@@"); // attach token to WCF request - client.ChannelFactory.Endpoint.Behaviors.Add(new AttachTokenEndpointBehavior(token)); + client.ChannelFactory.Endpoint.Behaviors.Add(new AttachTokenEndpointBehavior(result)); **Congratulations!** diff --git a/docs/webapi.md b/docs/webapi.md index f7eb1635..96513499 100644 --- a/docs/webapi.md +++ b/docs/webapi.md @@ -1,78 +1,12 @@ -# Using Auth0 with ASP.NET Web API and Single Page Apps +# Using Auth0 in ASP.NET Web API -Integrating Auth0 with a Single Page Application (SPA) and a Web API backend is very simple and straightforward. +@@includes.apinote@@ -##Before you start - -1. You will need Visual Studio 2012 and MVC4 - -> You can also browse and download the [sample code on GitHub](https://github.com/auth0/auth0-webapi-js-sample) - -##Integrating Auth0 with MVC4 - -###1. Create a simple MVC4 website - -For this example, we will use the empty MVC4 Empty Template. Select __"FILE -> New project -> ASP.NET MVC 4 Web Application -> Empty"__ - -Once the default template unfolds, create a new Controller that derives from `ApiController`. - - public class CustomersController : ApiController - { - // GET api/customers - public IEnumerable Get() - { - return new string[] { "John Doe", "Nancy Davolo" }; - } - } - -You can also create a `HomeController` with an `Index` method and the `Index.cshtml` view that will be the shell for your JavaScript Single Page App. - -> A regular HTML file would also work. - -### 2. Setting up the callback URL in Auth0 - -
      -

      After authenticating the user on Auth0, we will do a redirect to a URL on your web site. For security purposes, you have to register the callback URL of your website on the Application Settings section on Auth0 Admin app. For this type of application, the URL might look like this:

      - -
      http://localhost:some_random_port
      -
      - - -### 3. Triggering login manually or integrating the Auth0 widget - -@@sdk@@ - -You can use the returned `access_token` to make an AJAX call to Auth0 API. For example, this code below will return the logged user information: - - var access_token = /access_token=([^&]*)/g.exec(window.location.hash); - if (access_token) { - $.ajax({ - url: 'https://@@account.namespace@@/userinfo?access_token=' + access_token[1], - dataType: 'json', - success: function (data, status, jqHXR) { - // data will be a JSON containing all the user properties - }, - error: function (resp) { - if (resp.status == 401) { - // 'Unauthorized' - } else { - // error - } - } - }); - } - -> See the [User Profile](user-profile) document for details on the object returned. - -###4. Securing the Web API - -Auth0 will also give you a JSON Web Token which has been signed with your client secret. You should send this token in the `Authorizaton` header of your AJAX calls and validate it on the Web API. To accomplish this, we created a simple nuget package that will provide the JSON Web Token validation. - -####Run the following command on the Nuget Package Manager Console: +Install the following NuGet package Install-Package WebApi.JsonWebToken -####Add the following code snippet on the `Register` method of `WebApiConfig.cs`: +Add the following code snippet on the `Register` method of `WebApiConfig.cs`: config.MessageHandlers.Add(new JsonWebTokenValidationHandler() { @@ -80,9 +14,7 @@ Auth0 will also give you a JSON Web Token which has been signed with your client SymmetricKey = "@@account.clientSecret@@" // client secret }); -> It would be advisable to put these properties in your all config (Web.config) - -####Protect your Web API with the `[Authorize]` attribute +Protect your Web API with the `[Authorize]` attribute public class CustomersController : ApiController { @@ -92,50 +24,16 @@ Auth0 will also give you a JSON Web Token which has been signed with your client ... } -###5. Calling the secured API - -The last step would be to call the API from your JavaScript application. To do so, you have to extract the `id_token` from the URL hash, and send it to your API as part of the Authorization header (e.g. `Authorization: Bearer ...id_token...`). Here is some code to do that: - - var id_token = /id_token=([^&]*)/g.exec(window.location.hash); - $.ajax({ - url: '/api/customers', - dataType: 'json', - beforeSend: function (request) { - if (id_token) request.setRequestHeader("Authorization", "Bearer " + id_token[1]); - }, - success: function (data, status, jqHXR) { - // data has API response - }, - error: function (resp) { - if (resp.status == 401) { - // the token was invalid, not authorized - } else { - // server error - } - - } - }); - -###6. Testing the app: - -Open a browser, navigate to the website and press the login button. You should see Auth0 widget with a Google button, which is the default connection. - -Once you are logged in, you can try calling the API with and without the Authorization header to make sure things are properly configured. - - -### Some extra tips... - -You can get the user id on the Web API side by doing: - - ClaimsPrincipal.Current.Claims.SingleOrDefault(c => c.Type == "sub").Value - -If you want to get all the claims from the user (not just the id), you should specify `openid profile` (instead of just `openid`) in the scope parameter: +You can get the attributes of the user on the Web API side by doing: - + ClaimsPrincipal.Current.Claims.SingleOrDefault(c => c.Type == "email").Value -> Notice that this will add more user attributes to the token, and consequently increase the size of it. Some browsers have limits on URL lengths. +## Consuming the secure API -You are done! Congratulations! +* If you are building a Single Page / HTML5 Application, make sure to read [Using Auth0 in Single Page Apps / HTML5](singlepageapp-tutorial) to understand how to get a token on your application and send it to the Web API. +* If you are building a regular web site then you might want to read [Using Auth0 with ASP.NET](aspnet-tutorial). +* If you want to call this API from a console application or a powershell script, you could create a database connection to store credentials and obtain JSON Web Tokens based on those credentials as described in [Protocols - OAuth Resource Owner Password Credentials Grant](protocols#9). +* If you want to call this API from a WPF/Winforms app, read [Using Auth0 with WPF or Winforms](wpf-winforms-tutorial). ## Download the sample diff --git a/docs/weibo-clientid.md b/docs/weibo-clientid.md new file mode 100644 index 00000000..fad2bfac --- /dev/null +++ b/docs/weibo-clientid.md @@ -0,0 +1,20 @@ +# Obtaining a App ID and App Secret for Weibo + +To configure a Weibo connection you will need to register Auth0 on the [Weibo App portal](http://open.weibo.com/apps). + +##1. Add a new Application +Log in into [Weibo portal](http://open.weibo.com/apps), register a new application: + +![](//cdn.auth0.com/docs/img/weibo-register-1.png) + +--- +##2. Callback URLs + +When asked to enter OAuth2 callback URLs use: + + https://@@account.namespace@@/login/callback + +--- +##3. Get your App ID and App Secret + +Once the project is created, enter your new `App ID` and `App Secret` into the Yahoo! connection settings in Auth0. diff --git a/docs/what-to-do-once-the-user-is-logged-in/adding-scopes-for-an-external-idp.md b/docs/what-to-do-once-the-user-is-logged-in/adding-scopes-for-an-external-idp.md new file mode 100644 index 00000000..c73fd2da --- /dev/null +++ b/docs/what-to-do-once-the-user-is-logged-in/adding-scopes-for-an-external-idp.md @@ -0,0 +1,11 @@ +# Adding scopes/permissions to call an external IdP's APIs + +The user is logged in. This means we can get the user's profile and his `accessToken` so that we can call the IdP (Facebook, Github, etc.) APIs. + +> You can read [this article](calling-an-external-idp-api) If you don't know how to get the `accessToken` or how to call an IdP's API + +However, when we try to call the API, we're getting Access Denied. This is probably because we haven't asked for the right permissions to the user when he was logging in. + +In order to configure what scopes/permissions we need from the user, we must go to the [Connections](https://app.auth0.com/#/connections/social) section in Auth0 Dashboard. In there, we can expand any of the Identity Providers and then choose the particular scopes we need. For example, for facebook, we can add new scopes as follows: + +![Scopes for facebook](https://cdn.auth0.com/docs/scopes.gif) diff --git a/docs/what-to-do-once-the-user-is-logged-in/calling-an-external-idp-api.md b/docs/what-to-do-once-the-user-is-logged-in/calling-an-external-idp-api.md new file mode 100644 index 00000000..87117f7f --- /dev/null +++ b/docs/what-to-do-once-the-user-is-logged-in/calling-an-external-idp-api.md @@ -0,0 +1,53 @@ +# Calling an external IdP (Facebook, Github, etc.) API. + +**The user is logged in. That means that we can get the [user profile](https://docs.auth0.com/user-profile).** + +If we're using [Lock](https://github.com/auth0/Lock) or [Auth0.js](https://github.com/auth0/Auth0.js) from the client side, we'll get the profile in the callback after the user is logged in. +If we're using any SDK from the server side, we probably can access the profile from either the request or the session. + +The profile will look as follows: + +````json +{ + "email": "johnfoo@gmail.com", + "email_verified": true, + "family_name": "Foo", + "gender": "male", + "given_name": "John", + "identities": [ + { + "access_token": "ya29.AsaS6ZQgRHlCHqzZ3....sFFBpQYpVVieSWur-7tmZbzEtwMkA", + "provider": "google-oauth2", + "user_id": "103547991597142817347", + "connection": "google-oauth2", + "isSocial": true + } + ], + "locale": "en", + "name": "John Foo", + "nickname": "matiasw", + "picture": "https://lh4.googleusercontent.com/-OdsbOXom9qE/AAAAAAAAAAI/AAAAAAAAADU/_j8SzYTOJ4I/photo.jpg", + "user_id": "google-oauth2|103547991597142817347" +} +``` + +As you can see, **there's an array called `identities`**. In there, we'll get **the `accessToken`** of the different Identity Providers the user has used to log in. + +> Most of the times, there's going to be just one, but if you've used [account linking feature](https://docs.auth0.com/link-accounts) there might be more than one. + +The `accessToken` we get here will have access to call all the APIs we've specified we need in Auth0 dashboard. + +> You can read [this article](adding-scopes-for-an-external-idp) If you need to add more scopes/permissions to call other APIs. + +Let's just use the `accessToken` then! For example, if we're using lock we can do as follows: + +````js +lock.show(function(err, token, profile) { + // Assuming the user can ONLY log in with Google + // We're sure the first identity is Google + var googleToken = profile.identities[0].accessToken; + + // Function to call Google's API with the accessToken + getGoogleContactsWithToken(googleToken); +}) +``` diff --git a/docs/what-to-do-once-the-user-is-logged-in/index.md b/docs/what-to-do-once-the-user-is-logged-in/index.md new file mode 100644 index 00000000..13283432 --- /dev/null +++ b/docs/what-to-do-once-the-user-is-logged-in/index.md @@ -0,0 +1,7 @@ +# After Login + +Auth0 makes Authentication easy for your Apps and APIs. However, once the user is logged in, there're still tons of things you can do with the help of Auth0. In this section, we'll enumerate different articles explaining things to do beyond authentication: + +* [Calling an API from an IdP (Facebook, Twitter, Google)](calling-an-external-idp-api) +* [Adding more scopes and permissions to call other APIs from the IdPs](adding-scopes-for-an-external-idp) + diff --git a/docs/win8-cs-tutorial.md b/docs/win8-cs-tutorial.md index 9e7f7c20..62baeb62 100644 --- a/docs/win8-cs-tutorial.md +++ b/docs/win8-cs-tutorial.md @@ -13,51 +13,48 @@ Use the NuGet Package Manager (Tools -> Library Package Manager -> Package Manag ### 2. Setting up the callback URL in Auth0
      -

      Go to the Application Settings section on Auth0 Admin app and make sure that App Callbacks URLs has the following value:

      +

      Go to the Application Settings section in the Auth0 dashboard and make sure that Allowed Callback URLs contains the following value:

      https://@@account.namespace@@/mobile
      ### 3. Integration -There are three options to do the integration: +There are three options to do the integration: -1. Using the [Auth0 Login Widget](login-widget) inside a Web View (this is the simplest with only a few lines of code required). +1. Using the [Auth0 Login Widget](login-widget2) inside a Web View (this is the simplest with only a few lines of code required). 2. Creating your own UI (more work, but higher control the UI and overall experience). 3. Using specific user name and password. #### Option 1: Authentication using Login Widget -To start with, we'd recommend using the __Login Widget__. Here is a snippet of code to copy & paste on your project: +To start with, we'd recommend using the __Login Widget__. Here is a snippet of code to copy & paste on your project. +Since we are using `await` (.NET 4.5 or greater), your method needs to be `async`: ```csharp using Auth0.SDK; var auth0 = new Auth0Client( - "@@account.tenant@@", - "@@account.clientId@@", - "@@account.clientSecret@@"); - -auth0.LoginAsync (this) - .ContinueWith(t => { - /* - Use t.Result to do wonderful things, e.g.: - - get user email => t.Result.Profile["email"].ToString() - - get facebook/google/twitter/etc access token => t.Result.Profile["identities"][0]["access_token"] - - get Windows Azure AD groups => t.Result.Profile["groups"] - - etc. - */ }, - TaskScheduler.FromCurrentSynchronizationContext()); + "@@account.namespace@@", + "@@account.clientId@@"); + +var user = await auth0.LoginAsync(); +/* + Use this object to do wonderful things, e.g.: + - get user email => user.Profile["email"].ToString() + - get facebook/google/twitter/etc access token => user.Profile["identities"][0]["access_token"] + - get Windows Azure AD groups => user.Profile["groups"] + - etc. +*/ ``` -![](img/win8-cs-step1.png) +![](//cdn.auth0.com/docs/img/win8-cs-step1.png) #### Option 2: Authentication with your own UI -If you know which identity provider you want to use, you can add a `connection` parameter to the constructor and the user will be sent straight to the specified `connection`: +If you know which identity provider you want to use, you can add a `connection` parameter and the user will be sent straight to the specified `connection`: ```csharp -auth0.LoginAsync (this, "auth0waadtests.onmicrosoft.com") // connection name here - .ContinueWith(t => { /* Use t.Result to do wonderful things */ }); +var user = await auth0.LoginAsync("auth0waadtests.onmicrosoft.com")' // connection name here ``` > connection names can be found on Auth0 dashboard. E.g.: `facebook`, `linkedin`, `somegoogleapps.com`, `saml-protocol-connection`, etc. @@ -65,24 +62,26 @@ auth0.LoginAsync (this, "auth0waadtests.onmicrosoft.com") // connection name her #### Option 3: Authentication with specific user name and password (only for providers that support this) ```csharp -auth0.LoginAsync ( +var user = await auth0.LoginAsync( "my-db-connection", // connection name here - "username", // user name - "password") // password - .ContinueWith(t => - { - /* Use t.Result to do wonderful things */ - }); + "username", + "password"); ``` +#### Scope + +Optionally you can specify the `scope` parameter. There are two possible values for scope today: + +* __scope: "openid"__ _(default)_ - It will return, not only the `access_token`, but also an `id_token` which is a Json Web Token (JWT). The JWT will only contain the user id. +* __scope: "openid profile"__ - If you want the entire user profile to be part of the `id_token`. + ## Accessing user information The `Auth0User` has the following properties: * `Profile`: returns a `Newtonsoft.Json.Linq.JObject` object (from [Json.NET component](http://components.xamarin.com/view/json.net/)) containing all available user attributes (e.g.: `user.Profile["email"].ToString()`). -* `IdToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity (or Windows Azure Mobile Services, see below). +* `IdToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity. * `Auth0AccessToken`: the `access_token` that can be used to access Auth0's API. You would use this for example to [link user accounts](link-accounts). -> If you want to use __Windows Azure Mobile Services__ (WAMS) you should create a WAMS app in Auth0 and set the Master Key that you can get on the Windows Azure portal. Then you have change your Windows Store App to use the client id and secret of the WAMS app just created and set the callback of the WAMS app to be `https://@@account.tenant@@.auth0.com/mobile`. Finally, you have to set the `MobileServiceAuthenticationToken` property of the `MobileServiceUser` with the `IdToken` property of `Auth0User`. -**Congratulations!** \ No newline at end of file +**Congratulations!** diff --git a/docs/win8-tutorial.md b/docs/win8-tutorial.md index 4b961f2d..bdf522a4 100644 --- a/docs/win8-tutorial.md +++ b/docs/win8-tutorial.md @@ -4,13 +4,13 @@ This tutorial explains how to integrate Auth0 with a Windows App Store. `Auth0.W ## Tutorial -### 1. Install Auth0.Windows8.Cs NuGet package +### 1. Install Auth0.Windows8.Js NuGet package Use the NuGet Package Manager (Tools -> Library Package Manager -> Package Manager Console) to install the Auth0.Windows8.Js package, running the command:
      Install-Package Auth0.Windows8.Js
      -And add reference to the JavaScript code in the __default.html__, include the following line in the `` element: +And add reference to the JavaScript code in the __default.html__, include the following line in the `` element: ```html @@ -19,33 +19,32 @@ And add reference to the JavaScript code in the __default.html__, include the fo ### 2. Setting up the callback URL in Auth0
      -

      Go to the Application Settings section on Auth0 Admin app and make sure that App Callbacks URLs has the following value:

      +

      Go to the Application Settings section on Auth0 Admin app and make sure that App Callbacks URLs has the following value:

      https://@@account.namespace@@/mobile
      ### 3. Integration -There are three options to do the integration: +There are three options to do the integration: -1. Using the [Auth0 Login Widget](login-widget) inside a Web View (this is the simplest with only a few lines of code required). +1. Using the [Auth0 Login Widget](login-widget2) inside a Web View (this is the simplest with only a few lines of code required). 2. Creating your own UI (more work, but higher control the UI and overall experience). 3. Using specific user name and password. #### Option 1: Authentication using Login Widget -To start with, we'd recommend using the __Login Widget__. Here is a snippet of code to copy & paste on your project: +To start with, we'd recommend using the __Login Widget__. Here is a snippet of code to copy & paste on your project: ```javascript var auth0 = new Auth0Client( - "@@account.tenant@@", - "@@account.clientId@@", - "@@account.clientSecret@@"); + "@@account.namespace@@", + "@@account.clientId@@"); auth0.Login(function (err, result) { if (err) return err; - /* - Use result to do wonderful things, e.g.: + /* + Use result to do wonderful things, e.g.: - get user email => result.Profile.email - get facebook/google/twitter/etc access token => result.Profile.identities[0].access_token - get Windows Azure AD groups => result.Profile.groups @@ -54,17 +53,17 @@ auth0.Login(function (err, result) { }); ``` -![](img/win8-cs-step1.png) +![](//cdn.auth0.com/docs/img/win8-cs-step1.png) #### Option 2: Authentication with your own UI -If you know which identity provider you want to use, you can add a `connection` parameter to the constructor and the user will be sent straight to the specified `connection`: +If you know which identity provider you want to use, you can add a `connection` parameter and the user will be sent straight to the specified `connection`: ```javascript -auth0.Login("auth0waadtests.onmicrosoft.com", function (err, result) { +auth0.Login({ connection: "auth0waadtests.onmicrosoft.com" }, function (err, result) { if (err) return err; - /* - Use result to do wonderful things, e.g.: + /* + Use result to do wonderful things, e.g.: - get user email => result.Profile.email - get facebook/google/twitter/etc access token => result.Profile.identities[0].access_token - get Windows Azure AD groups => result.Profile.groups @@ -78,14 +77,15 @@ auth0.Login("auth0waadtests.onmicrosoft.com", function (err, result) { #### Option 3: Authentication with specific user name and password (only for providers that support this) ```javascript -auth0.Login( - "my-db-connection", - "username", - "password", +auth0.Login({ + connection: "my-db-connection", + username: "username", + password: "password" + }, function (err, result) { if (err) return err; - /* - Use result to do wonderful things, e.g.: + /* + Use result to do wonderful things, e.g.: - get user email => result.Profile.email - get facebook/google/twitter/etc access token => result.Profile.identities[0].access_token - get Windows Azure AD groups => result.Profile.groups @@ -94,14 +94,20 @@ auth0.Login( }); ``` +#### Scope + +Optionally you can specify the `scope` parameter. There are two possible values for scope today: + +* __scope: "openid"__ _(default)_ - It will return, not only the `access_token`, but also an `id_token` which is a Json Web Token (JWT). The JWT will only contain the user id. +* __scope: "openid profile"__ - If you want the entire user profile to be part of the `id_token`. + ## Accessing user information The `Auth0User` has the following properties: * `Profile`: returns a JSON object containing all available user attributes (e.g.: `user.Profile.email`). -* `IdToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity (or Windows Azure Mobile Services, see below). +* `IdToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity. * `Auth0AccessToken`: the `access_token` that can be used to access Auth0's API. You would use this for example to [link user accounts](link-accounts). -> If you want to use __Windows Azure Mobile Services__ (WAMS) you should create a WAMS app in Auth0 and set the Master Key that you can get on the Windows Azure portal. Then you have change your Windows Store App to use the client id and secret of the WAMS app just created and set the callback of the WAMS app to be `https://@@account.tenant@@.auth0.com/mobile`. Finally, you have to set the `MobileServiceAuthenticationToken` property of the `MobileServiceUser` with the `IdToken` property of `Auth0User`. -**Congratulations!** \ No newline at end of file +**Congratulations!** diff --git a/docs/windowsphone-tutorial.md b/docs/windowsphone-tutorial.md index 5bcd9226..bac75424 100644 --- a/docs/windowsphone-tutorial.md +++ b/docs/windowsphone-tutorial.md @@ -6,57 +6,55 @@ This tutorial explains how to integrate Auth0 with a Windows Phone app. `Auth0.W ### 1. Install Auth0.WindowsPhone NuGet package -Use the NuGet Package Manager (Tools -> Library Package Manager -> Package Manager Console) to install the Auth0.Windows8.Js package, running the command: +Use the NuGet Package Manager (Tools -> Library Package Manager -> Package Manager Console) to install the Auth0.WindowsPhone package, running the command:
      Install-Package Auth0.WindowsPhone
      ### 2. Setting up the callback URL in Auth0
      -

      Go to the Application Settings section on Auth0 Admin app and make sure that App Callbacks URLs has the following value:

      +

      Go to the Application Settings section in the Auth0 dashboard and make sure that Allowed Callback URLs contains the following value:

      https://@@account.namespace@@/mobile
      ### 3. Integration -There are three options to do the integration: +There are three options to do the integration: -1. Using the [Auth0 Login Widget](login-widget) inside a Web View (this is the simplest with only a few lines of code required). +1. Using the [Auth0 Login Widget](login-widget2) inside a Web View (this is the simplest with only a few lines of code required). 2. Creating your own UI (more work, but higher control the UI and overall experience). 3. Using specific user name and password. #### Option 1: Authentication using Login Widget -To start with, we'd recommend using the __Login Widget__. Here is a snippet of code to copy & paste on your project: +To start with, we'd recommend using the __Login Widget__. Here is a snippet of code to copy & paste on your project: ```csharp using Auth0.SDK; var auth0 = new Auth0Client( - "@@account.tenant@@", - "@@account.clientId@@", - "@@account.clientSecret@@"); - -auth0.LoginAsync (this) - .ContinueWith(t => { - /* - Use t.Result to do wonderful things, e.g.: - - get user email => t.Result.Profile["email"].ToString() - - get facebook/google/twitter/etc access token => t.Result.Profile["identities"][0]["access_token"] - - get Windows Azure AD groups => t.Result.Profile["groups"] - - etc. - */ }); + "@@account.namespace@@", + "@@account.clientId@@"); + +var user = await auth0.LoginAsync(); +/* +- get user email => user.Profile["email"].ToString() +- get facebook/google/twitter/etc access token => user.Profile["identities"][0]["access_token"] +- get Windows Azure AD groups => user.Profile["groups"] +- etc. +*/ ``` -![](img/windows-phone-tutorial.png) +Invoking LoginAsync without parameters will show the login widget: + +![](//cdn.auth0.com/docs/img/windows-phone-tutorial.png) #### Option 2: Authentication with your own UI -If you know which identity provider you want to use, you can add a `connection` parameter to the constructor and the user will be sent straight to the specified `connection`: +If you know which identity provider you want to use, you can add a `connection` parameter and the user will be sent straight to the specified `connection`: ```csharp -auth0.LoginAsync (this, "auth0waadtests.onmicrosoft.com") // connection name here - .ContinueWith(t => { /* Use t.Result to do wonderful things */ }); +var user = await auth0.LoginAsync("auth0waadtests.onmicrosoft.com") // connection name here ``` > connection names can be found on Auth0 dashboard. E.g.: `facebook`, `linkedin`, `somegoogleapps.com`, `saml-protocol-connection`, etc. @@ -64,14 +62,11 @@ auth0.LoginAsync (this, "auth0waadtests.onmicrosoft.com") // connection name her #### Option 3: Authentication with specific user name and password (only for providers that support this) ```csharp -auth0.LoginAsync ( +var user = await auth0.LoginAsync ( "my-db-connection", // connection name here "username", // user name "password") // password - .ContinueWith(t => - { - /* Use t.Result to do wonderful things */ - }); + ``` ## Accessing user information @@ -79,9 +74,13 @@ auth0.LoginAsync ( The `Auth0User` has the following properties: * `Profile`: returns a `Newtonsoft.Json.Linq.JObject` object (from [Json.NET component](http://components.xamarin.com/view/json.net/)) containing all available user attributes (e.g.: `user.Profile["email"].ToString()`). -* `IdToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity (or Windows Azure Mobile Services, see below). +* `IdToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity. * `Auth0AccessToken`: the `access_token` that can be used to access Auth0's API. You would use this for example to [link user accounts](link-accounts). -> If you want to use __Windows Azure Mobile Services__ (WAMS) you should create a WAMS app in Auth0 and set the Master Key that you can get on the Windows Azure portal. Then you have change your Windows Phone app to use the client id and secret of the WAMS app just created and set the callback of the WAMS app to be `https://@@account.tenant@@.auth0.com/mobile`. Finally, you have to set the `MobileServiceAuthenticationToken` property of the `MobileServiceUser` with the `IdToken` property of `Auth0User`. +## Logout + +You can trigger a logout by doing. This will clean up the cookies of the embedded browser. + + await auth0.LogoutAsync(); -**Congratulations!** \ No newline at end of file +**Congratulations!** diff --git a/docs/windowsstore-auth0-tutorial.md b/docs/windowsstore-auth0-tutorial.md index 979b8ca3..546a58c6 100644 --- a/docs/windowsstore-auth0-tutorial.md +++ b/docs/windowsstore-auth0-tutorial.md @@ -3,13 +3,13 @@ title: Windows Store Auth0 Tutorial layout: doc.nosidebar.tutorial --- -# Authenticating Users Anywhere with Auth0 +# Authenticating Users Anywhere with Auth0 ## Goals 1. Build a Windows Store app that authenticates users with any identity provider (e.g. Facebook, Google, MS Account, Twitter, PayPal, LinkedIn, Office365, Google Apps, etc) -2. Once authenticated, call Auth0 API to retrieve user's information. +2. Once authenticated, call Auth0 API to retrieve user's information. > The tutorial will take 10 minutes approximately. You are just 10 minutes away from the (W) token :). You shouldn't, but if you need help: http://chat.auth0.com @@ -62,7 +62,7 @@ Make the class public, and add the following properties and a constructor: private string Callback { get; set; } public string AuthenticationToken { get; set; } - public string AccessToken { get; set; } + public string AccessToken { get; set; } public Auth0Client(string tenant, string clientId, string callback) { @@ -151,7 +151,7 @@ When you run the application you will see the Login Screen with the __Auth0 Logi It will show "Google" as an option to authenticate. Place a breakpoint in the `var token=ts.Result` line and complete authentication. If everything is successful, you will see the `access_token`. -###6. Enable other identity providers +###6. Enable other identity providers Go back to [Auth0](https://app.auth0.com) and select __Connections__, __Social__: @@ -167,7 +167,7 @@ Run the app again and you will see the providers you just enabled on the login s Open the `MainPage.xaml` file and drop a `TextBox` control from the Toolbox after the `Button`, name it `UserInfo`, clean `Text` and set `IsReadOnly` to `true`: -![](img/windowsstore-step7.1.png) +![](//cdn.auth0.com/docs/img/windowsstore-step7.1.png) Open the `Auth0Client.cs` file and add the __GetUserInfoAsync__ method: @@ -201,7 +201,7 @@ Go to the Button_Click event handler and replace `var token = ts.Result;` to the ```cs -client.GetUserInfoAsync().ContinueWith(task => +client.GetUserInfoAsync().ContinueWith(task => { this.UserInfo.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => this.UserInfo.Text = task.Result); }); @@ -209,6 +209,6 @@ client.GetUserInfoAsync().ContinueWith(task => Run the app again, and you will see the user info after login: -![](img/windowsstore-step7.png) +![](//cdn.auth0.com/docs/img/windowsstore-step7.png) Congratulations!! diff --git a/docs/windowsstore-js-auth0-tutorial.md b/docs/windowsstore-js-auth0-tutorial.md index 0b57b830..2de3495e 100644 --- a/docs/windowsstore-js-auth0-tutorial.md +++ b/docs/windowsstore-js-auth0-tutorial.md @@ -3,13 +3,13 @@ title: Windows Store Javascript Auth0 Tutorial layout: doc.nosidebar.tutorial --- -# Authenticating Users Anywhere with Auth0 +# Authenticating Users Anywhere with Auth0 ## Goals 1. Build a Windows Store app that authenticates users with any identity provider (e.g. Facebook, Google, MS Account, Twitter, PayPal, LinkedIn, Office365, Google Apps, etc) -2. Once authenticated, call Auth0 API to retrieve user's information. +2. Once authenticated, call Auth0 API to retrieve user's information. > The tutorial will take 10 minutes approximately. You are just 10 minutes away from the (W) token :). You shouldn't, but if you need help: http://chat.auth0.com @@ -38,7 +38,7 @@ Keep Auth0 open. You will later need information from the dashboard to test your ###1. Open Visual Studio 2012 and create a new blank Windows Store App: -![](img/windowsstore-javascript-step1.png) +![](//cdn.auth0.com/docs/img/windowsstore-javascript-step1.png) ###2. Add a basic UI to the app @@ -49,13 +49,13 @@ Open the `default.html` file and replace `

      Content goes here

      ` to a `button ``` -![](img/windowsstore-javascript-step2.png) +![](//cdn.auth0.com/docs/img/windowsstore-javascript-step2.png) ###3. Add the auth0Client.js javascript This javascript will encapsulate all access to Auth0. Add a new file named `auth0Client.js` under the `js` folder: -![](img/windowsstore-javascript-step3.png) +![](//cdn.auth0.com/docs/img/windowsstore-javascript-step3.png) And add the following code: @@ -113,11 +113,11 @@ var page = WinJS.UI.Pages.define("/default.html", { When you run the application you will see the Login Screen with the __Auth0 Login Widget__: -![](img/windowsstore-javascript-step5.png) +![](//cdn.auth0.com/docs/img/windowsstore-javascript-step5.png) It will show "Google" as an option to authenticate. Place a breakpoint in the `var access_token = result.responseData.split("#")[1].split("&")[1].split("=")[0];` line and complete authentication. If everything is successful, you will see the `access_token`. -###6. Enable other identity providers +###6. Enable other identity providers Go back to [Auth0](https://app.auth0.com) and select __Connections__, __Social__: @@ -127,7 +127,7 @@ Enable any of the providers available by clicking on the `Disabled` button. Run the app again and you will see the providers you just enabled on the login screen: -![](img/windowsstore-javascript-step6.2.png) +![](//cdn.auth0.com/docs/img/windowsstore-javascript-step6.2.png) ###7. Getting user attributes @@ -163,6 +163,6 @@ function log(msg) { Run the app again and you will see the user info after login: -![](img/windowsstore-javascript-step7.png) +![](//cdn.auth0.com/docs/img/windowsstore-javascript-step7.png) ## Congratulations!! Go get your token! diff --git a/docs/wordpress-clientid.md b/docs/wordpress-clientid.md new file mode 100644 index 00000000..b2ae2a36 --- /dev/null +++ b/docs/wordpress-clientid.md @@ -0,0 +1,27 @@ +# Obtaining a Client ID and Client Secret for WordPress + +To configure WordPress OAuth2 connections you will need to register Auth0 with WordPress on their [developer portal](http://developer.wordpress.com/). + +##1. Log in into the developer portal +Go to the [developer portal](http://developer.wordpress.com/), and login with your WordPress credentials. Select __My Applications__ and click on __Create New Application__: + +![](//cdn.auth0.com/docs/img/wordpress-devportal-1.png) + +--- + +##2. Complete information about your instance of Auth0 + +Create a new application and complete the form. Use this URL as your callback: + + https://@@account.namespace@@/login/callback + +![](//cdn.auth0.com/docs/img/wordpress-devportal-2.png) + +--- + +##4. Get your Client ID and Client Secret + +Once the application is registered, enter your new `Client ID` and `Client Secret` into the connection settings in Auth0. + +![](//cdn.auth0.com/docs/img/wordpress-devportal-3.png) + diff --git a/docs/workshop.md b/docs/workshop.md new file mode 100644 index 00000000..98f9bb70 --- /dev/null +++ b/docs/workshop.md @@ -0,0 +1,68 @@ +# Auth0 Workshop + +These are the exercises for the __Auth0 Workshop__. + +## Exercise 1 + +Create a new ASP.Net application and register it in Auth0. +Follow the tutorial until step 4. Use the code snippet of the Step 4 to replace the login link functionality. + +## Exercise 2 + +Add the Logout functionality as explained in the tutorial. + +## Exercise 3 + +Make the About page secure with the `[Authorize]` attribute. +Replace the content of loginForm in Login.cshtml and show the sign in widget there. + +__Hint:__ use `container: 'loginForm'` and `chrome: true`. + +## Exercise 4 + +Use the `state` parameter to keep the URL the user wants to access. + +__Hint:__ add this option to the signin call `state: 'ru=@Request.QueryString["ReturnUrl"]'` in the Login.cshtml. + +## Exercise 5 + +Show every claim in the About page. + +Add `ViewBag.User = ClaimsPrincipal.Current.Claims` and then in the About.cshtml: + +
        + @foreach (var claim in ViewBag.User) + { +
      • @claim.Type : @claim.Value
      • + } +
      + +## Exercise 6 + +To extend the profile metadata create a new form in the About page asking for `Telephone` and POSTs the value to `/Profile` in your ASP.Net application. +Using an instance of Auth0 client with the global client id and secret, update the current user's metadata. +Verify next time the user logins has this new claim in the About page. + +__Hint:__ user_id is another claim. +__Hint:__ found global client_id and client_secret on API Explorer. + +## Exercise 7 + +Create a new WCF Project and Service. Add a call to the new service from the ASP.Net application. + +## Exercise 8 + +Follow the WCF Tutorial in order to secure your web service. + +## Exercise 9 + +Create a new WCF application in Auth0. +Change the Client ID and Secret in the WCF service with the new credentials. +Lastly instead of attaching the original id_token, obtain a new __delegation token__ using the original one. + + // get JsonWebToken from logged in user + string token = ClaimsPrincipal.Current.FindFirst("id_token").Value; + + // create an Auth0 client to call the /delegation endpoint using the client id and secret of the caller application + var auth0 = new Auth0.Client("... asp.net client id ...", "... asp.net client secret ...", "... namespace ..."); + var result = auth0.GetDelegationToken(token, "...client id of the target WCF service..."); \ No newline at end of file diff --git a/docs/wpf-winforms-tutorial.md b/docs/wpf-winforms-tutorial.md index 81e38dae..a18ce796 100644 --- a/docs/wpf-winforms-tutorial.md +++ b/docs/wpf-winforms-tutorial.md @@ -13,53 +13,50 @@ Use the NuGet Package Manager (Tools -> Library Package Manager -> Package Manag ### 2. Setting up the callback URL in Auth0
      -

      Go to the Application Settings section on Auth0 Admin app and make sure that App Callbacks URLs has the following value:

      +

      Go to the Application Settings section in the Auth0 dashboard and make sure that Allowed Callback URLs contains the following value:

      https://@@account.namespace@@/mobile
      ### 3. Integration -There are three options to do the integration: +There are three options to do the integration: -1. Using the [Auth0 Login Widget](login-widget) inside a Web View (this is the simplest with only a few lines of code required). +1. Using [Auth0 Lock](login-widget2) inside a Web View (this is the simplest with only a few lines of code required). 2. Creating your own UI (more work, but higher control the UI and overall experience). 3. Using specific user name and password. -#### Option 1: Authentication using Login Widget +#### Option 1: Authentication using Lock -To start with, we'd recommend using the __Login Widget__. Here is a snippet of code to copy & paste on your project: +To start with, we'd recommend using __Lock__. Here is a snippet of code to copy & paste on your project. +Since we are using `await` (.NET 4.5 or greater), your method needs to be `async`: ```csharp using Auth0.Windows; var auth0 = new Auth0Client( "@@account.namespace@@", - "@@account.clientId@@", - "@@account.clientSecret@@"); - -auth0.LoginAsync (this) - .ContinueWith(t => { - /* - Use t.Result to do wonderful things, e.g.: - - get user email => t.Result.Profile["email"].ToString() - - get facebook/google/twitter/etc access token => t.Result.Profile["identities"][0]["access_token"] - - get Windows Azure AD groups => t.Result.Profile["groups"] - - etc. - */ }, - TaskScheduler.FromCurrentSynchronizationContext()); + "@@account.clientId@@"); + +var user = await auth0.LoginAsync(this); +/* + Use this object to do wonderful things, e.g.: + - get user email => user.Profile["email"].ToString() + - get facebook/google/twitter/etc access token => user.Profile["identities"][0]["access_token"] + - get Windows Azure AD groups => user.Profile["groups"] + - etc. +*/ ``` -![](img/wpf-winforms-step1.png) +![](//cdn.auth0.com/docs/img/wpf-winforms-step1.png) -> For __WPF__ apps you should use `auth0.LoginAsync(new WindowWrapper(new WindowInteropHelper(this).Handle))` instead of `auth0.LoginAsync (this)` +> For __WPF__ apps you should use `auth0.LoginAsync(new WindowWrapper(new WindowInteropHelper(this).Handle))` instead of `auth0.LoginAsync(this)` #### Option 2: Authentication with your own UI -If you know which identity provider you want to use, you can add a `connection` parameter to the constructor and the user will be sent straight to the specified `connection`: +If you know which identity provider you want to use, you can add a `connection` parameter and the user will be sent straight to the specified `connection`: ```csharp -auth0.LoginAsync (this, "auth0waadtests.onmicrosoft.com") // connection name here - .ContinueWith(t => { /* Use t.Result to do wonderful things */ }); +var user = await auth0.LoginAsync(this, "auth0waadtests.onmicrosoft.com") // connection name here ``` > connection names can be found on Auth0 dashboard. E.g.: `facebook`, `linkedin`, `somegoogleapps.com`, `saml-protocol-connection`, etc. @@ -67,14 +64,10 @@ auth0.LoginAsync (this, "auth0waadtests.onmicrosoft.com") // connection name her #### Option 3: Authentication with specific user name and password ```csharp -auth0.LoginAsync ( +var user = await auth0.LoginAsync( "my-db-connection", // connection name here - "username", // user name - "password") // password - .ContinueWith(t => - { - /* Use t.Result to do wonderful things */ - }); + "username", + "password"); ``` ## Accessing user information @@ -82,14 +75,11 @@ auth0.LoginAsync ( The `Auth0User` has the following properties: * `Profile`: returns a `Newtonsoft.Json.Linq.JObject` object (from [Json.NET component](http://components.xamarin.com/view/json.net/)) containing all available user attributes (e.g.: `user.Profile["email"].ToString()`). -* `IdToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity (or Windows Azure Mobile Services, see below). +* `IdToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity. * `Auth0AccessToken`: the `access_token` that can be used to access Auth0's API. You would use this for example to [link user accounts](link-accounts). -> If you want to use __Windows Azure Mobile Services__ (WAMS) you should create a WAMS app in Auth0 and set the Master Key that you can get on the Windows Azure portal. Then you have change your WPF or Winforms app to use the client id and secret of the WAMS app just created and set the callback of the WAMS app to be `https://@@account.tenant@@.auth0.com/mobile`. Finally, you have to set the `MobileServiceAuthenticationToken` property of the `MobileServiceUser` with the `IdToken` property of `Auth0User`. - ## Download the samples Browse the samples on GitHub from [here](https://github.com/auth0/auth0-winforms-wpf-sample). - **Congratulations!** diff --git a/docs/wsfedwebapp-tutorial.md b/docs/wsfedwebapp-tutorial.md new file mode 100644 index 00000000..92c5b93d --- /dev/null +++ b/docs/wsfedwebapp-tutorial.md @@ -0,0 +1,3 @@ +# Using Auth0 in WS-Fed (WIF) Web App + +Please enable and configure `WS-Fed (WIF) Web App` from Application Add-ons section and follow the instructions displayed. \ No newline at end of file diff --git a/docs/xamarin-tutorial.md b/docs/xamarin-tutorial.md index 4022e2a7..e0fd5d93 100644 --- a/docs/xamarin-tutorial.md +++ b/docs/xamarin-tutorial.md @@ -17,53 +17,49 @@ For more information, please visit the -

      Go to the Application Settings section on Auth0 Admin app and make sure that App Callbacks URLs has the following value:

      +

      Go to the Application Settings section in the Auth0 dashboard and make sure that Allowed Callback URLs contains the following value:

      https://@@account.namespace@@/mobile
      ### 3. Integration -There are three options to do the integration: +There are three options to do the integration: -1. Using the [Auth0 Login Widget](login-widget) inside a Web View (this is the simplest with only a few lines of code required). +1. Using the [Auth0 Login Widget](login-widget2) inside a Web View (this is the simplest with only a few lines of code required). 2. Creating your own UI (more work, but higher control the UI and overall experience). 3. Using specific user name and password. #### Option 1: Authentication using Login Widget -To start with, we'd recommend using the __Login Widget__. Here is a snippet of code to copy & paste on your project: +To start with, we'd recommend using the __Login Widget__. Here is a snippet of code to copy & paste on your project: ```csharp using Auth0.SDK; var auth0 = new Auth0Client( - "@@account.tenant@@", - "@@account.clientId@@", - "@@account.clientSecret@@"); + "@@account.namespace@@", + "@@account.clientId@@"); // 'this' could be a Context object (Android) or UIViewController, UIView, UIBarButtonItem (iOS) -auth0.LoginAsync (this) - .ContinueWith(t => { - /* - Use t.Result to do wonderful things, e.g.: - - get user email => t.Result.Profile["email"].ToString() - - get facebook/google/twitter/etc access token => t.Result.Profile["identities"][0]["access_token"] - - get Windows Azure AD groups => t.Result.Profile["groups"] - - etc. - */ }); +var user = await auth0.LoginAsync(this); +/* +- get user email => user.Profile["email"].ToString() +- get facebook/google/twitter/etc access token => user.Profile["identities"][0]["access_token"] +- get Windows Azure AD groups => user.Profile["groups"] +- etc. +*/ ``` > `Xamarin.Auth0Client` is built on top of the `WebRedirectAuthenticator` in the Xamarin.Auth component. All rules for standard authenticators apply regarding how the UI will be displayed. -![](https://docs.auth0.com/img/xamarin.auth0client.png) +![](//cdn.auth0.com/docs/img/xamarin.auth0client.png) #### Option 2: Authentication with your own UI -If you know which identity provider you want to use, you can add a `connection` parameter to the constructor and the user will be sent straight to the specified `connection`: +If you know which identity provider you want to use, you can add a `connection` parameter and the user will be sent straight to the specified `connection`: ```csharp -auth0.LoginAsync (this, "google-oauth2") // connection name here - .ContinueWith(t => { /* Use t.Result to do wonderful things */ }); +var user = await auth0.LoginAsync(this, "google-oauth2"); // connection name here ``` > connection names can be found on Auth0 dashboard. E.g.: `facebook`, `linkedin`, `somegoogleapps.com`, `saml-protocol-connection`, etc. @@ -71,14 +67,10 @@ auth0.LoginAsync (this, "google-oauth2") // connection name here #### Option 3: Authentication with specific user name and password ```csharp -auth0.LoginAsync ( - "sql-azure-database", // connection name here - "jdoe@foobar.com", // user name - "1234") // password - .ContinueWith(t => - { - /* Use t.Result to do wonderful things */ - }); +var user = await auth0.LoginAsync( + "sql-azure-database", // connection name here + "jdoe@foobar.com", // user name + "1234"); // password ``` ## Accessing user information @@ -86,11 +78,9 @@ auth0.LoginAsync ( The `Auth0User` has the following properties: * `Profile`: returns a `Newtonsoft.Json.Linq.JObject` object (from [Json.NET component](http://components.xamarin.com/view/json.net/)) containing all available user attributes (e.g.: `user.Profile["email"].ToString()`). -* `IdToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity (or Windows Azure Mobile Services, see below). +* `IdToken`: is a Json Web Token (JWT) containing all of the user attributes and it is signed with your client secret. This is useful to call your APIs and flow the user identity. * `Auth0AccessToken`: the `access_token` that can be used to access Auth0's API. You would use this for example to [link user accounts](link-accounts). -> If you want to use __Windows Azure Mobile Services__ (WAMS) you should create a WAMS app in Auth0 and set the Master Key that you can get on the Windows Azure portal. Then you have change your Xamarin app to use the client id and secret of the WAMS app just created and set the callback of the WAMS app to be` https://@@account.tenant@@.auth0.com/mobile`. Finally, you have to set the `MobileServiceAuthenticationToken` property of the `MobileServiceUser` with the `IdToken` property of `Auth0User`. - ## Download the samples Browse the samples on GitHub from [here](https://github.com/auth0/Xamarin.Auth0Client/tree/master/samples). diff --git a/docs/yahoo-clientid.md b/docs/yahoo-clientid.md new file mode 100644 index 00000000..51035d39 --- /dev/null +++ b/docs/yahoo-clientid.md @@ -0,0 +1,30 @@ +# Obtaining a Consumer Key and Consumer Secret for Yahoo! + +To configure a Yahoo! connection you will need to register Auth0 on the [Yahoo! Developer Network portal](https://developer.yahoo.com/). + +##1. Add a new Project +Log in to [Yahoo! Developer Network portal](https://developer.yahoo.com/) and click on __Create an App__: + +![](//cdn.auth0.com/docs/img/yahoo-register-1.png) + +--- + +##2. Register a new project + +Complete the form. For __Access Scopes__ select "This app requires access to private user data." and use this URL for the __Callback Domain__: + + https://@@account.namespace@@/login/callback + +Also make sure to select at least one Yahoo API: + +![](//cdn.auth0.com/docs/img/yahoo-register-3.png) + +--- + +##3. Get your Consumer Key and Consumer Secret + +Once the project is created, enter your new `Consumer Key` a nd `Consumer Secret` into the Yahoo! connection settings in Auth0. + +![](//cdn.auth0.com/docs/img/yahoo-register-2.png) + + diff --git a/docs/yandex-clientid.md b/docs/yandex-clientid.md index 40d0dfa1..882bcf95 100644 --- a/docs/yandex-clientid.md +++ b/docs/yandex-clientid.md @@ -6,7 +6,7 @@ To configure an Yandex connection you will need to register your Auth0 instance Log in into Yandex and [create a new app](https://oauth.yandex.ru/client/new): -> Complete instructions are available [here](http://api.yandex.ru/oauth/doc/dg/tasks/register-client.xml) +> Complete instructions are available [here](http://api.yandex.ru/oauth/doc/dg/tasks/register-client.xml) --- @@ -14,7 +14,7 @@ Log in into Yandex and [create a new app](https://oauth.yandex.ru/client/new): Complete the form: -![](img/yandex-create-app.png) +![](//cdn.auth0.com/docs/img/yandex-create-app.png) The callback address for your app should be: @@ -29,5 +29,5 @@ Notice that `scopes` in Yandex are defined in this screen. Select what kind of i Once the application is registered, enter your new `Application ID` and `Application Password` into the connection settings in Auth0. -![](img/yandex-add-connection.png) +![](//cdn.auth0.com/docs/img/yandex-add-connection.png) diff --git a/lib/authorizations.js b/lib/authorizations.js new file mode 100644 index 00000000..12e4ec40 --- /dev/null +++ b/lib/authorizations.js @@ -0,0 +1,29 @@ +var authorizations = module.exports; +var getDb = require('./data'); +var clients = require('./clients'); + +authorizations.getUserTenants = function(userId, callback) { + getDb(function (db) { + db.collection('clients').distinct('tenant', { owners: userId }, function (err, tenants) { + if (err) { return callback(err); } + tenants.sort(); + callback(null, tenants); + }); + }); +}; + +authorizations.getTenantOwners = function(tenant, callback) { + clients.find({ tenant: tenant, global: true }, function (err, clients) { + if (err) { return callback(err); } + + var globalClient = clients && clients[0]; + + if (!globalClient || !globalClient.owners) { + return callback(null, []); + } + + getDb(function (db) { + db.collection('tenantUsers').find({ id: { $in: globalClient.owners } }).toArray(callback); + }); + }); +}; diff --git a/lib/clients.js b/lib/clients.js new file mode 100644 index 00000000..f220b626 --- /dev/null +++ b/lib/clients.js @@ -0,0 +1,104 @@ +var clients = module.exports; +var nconf = require('nconf'); +var utils = require('./utils'); +var getDb = require('./data'); + +var sensitiveFields = ['clientSecret']; + +function decryptSensitiveFields (client) { + if (!client || client.encrypted !== true) return client; + + var key = nconf.get('SENSITIVE_DATA_ENCRYPTION_KEY'); + sensitiveFields.forEach(function (f) { + if (client[f]) { + client[f] = utils.decryptAesSha256(key, client[f], true); + } + }); + + if (client.signingKey && client.signingKey.key) { + client.signingKey.key = utils.decryptAesSha256(key, client.signingKey.key, true); + } + + return client; +} + +function ensureFields (fields) { + if (!fields) return fields; + + ['encrypted'].forEach(function (f) { + if (fields[f] === 0) fields[f] = 1; + + Object.keys(fields).forEach(function (k) { + if (fields[k] === 1) { + fields[f] = 1; + return; + } + }); + }); + + return fields; +} + +clients.findByClientId = function(clientID, fields, callback) { + if (typeof fields === 'function') { + callback = fields; + fields = null; + } + + if (!nconf.get('db')) { + return callback(null, null); + } + + + var done = function (err, client) { + if (err) return callback(err); + callback(null, decryptSensitiveFields(client)); + }; + + getDb(function (db) { + if (fields) { + ensureFields(fields); + db.collection('clients').findOne({clientID: clientID}, fields, done); + } else { + db.collection('clients').findOne({clientID: clientID}, { signingKey: 0 }, done); + } + }); +}; + +clients.findByTenantAndClientId = function(tenant, clientID, fields, callback) { + if (typeof fields === 'function') { + callback = fields; + fields = null; + } + + var done = function (err, client) { + if (err) return callback(err); + callback(null, decryptSensitiveFields(client)); + }; + + getDb(function (db) { + if (fields) { + ensureFields(fields); + db.collection('clients').findOne({tenant: tenant, clientID: clientID}, fields, done); + } else { + db.collection('clients').findOne({tenant: tenant, clientID: clientID}, { signingKey: 0 }, done); + } + }); +}; + +clients.find = function (query, fields, callback) { + if (typeof fields === 'function') { + callback = fields; + fields = { _id: 0 }; + } + + var done = function (err, clients) { + if (err) return callback(err); + callback(null, clients.map(function (c) { return decryptSensitiveFields(c); })); + }; + + getDb(function (db) { + ensureFields(fields); + db.collection('clients').find(query, fields).toArray(done); + }); +}; diff --git a/lib/data/index.js b/lib/data/index.js index 0c6012e3..fee4cd9e 100644 --- a/lib/data/index.js +++ b/lib/data/index.js @@ -1,11 +1,18 @@ var nconf = require('nconf'); var getDb = require('mongo-getdb'); var url = require('url'); +var winston = require('winston'); +var MongoHeartbeat = require('mongo-heartbeat'); -var db_url = nconf.get("db"); - -var repl_set_name = url.parse(db_url, true).query.replicaSet; +if (!nconf.get('db')) { + module.exports = function () { + throw new Error('no db'); + }; + return; +} +var db_url = nconf.get('db'); +var repl_set_name = url.parse(db_url, true).query.replicaSet; var options = {}; var socketOptions = { @@ -31,4 +38,17 @@ if (repl_set_name) { getDb.init(db_url, options); -module.exports = getDb; \ No newline at end of file +module.exports = getDb; + +getDb(function(db) { + var hb = new MongoHeartbeat(db, { + interval: 10000, + timeout: 2000 + }); + hb.on('error', function () { + winston.error('cant connect to the database (mongo-heartbeat)'); + setTimeout(function () { + process.exit(1); + }, 2000); + }); +}); diff --git a/lib/default_callback.js b/lib/default_callback.js new file mode 100644 index 00000000..342b87ee --- /dev/null +++ b/lib/default_callback.js @@ -0,0 +1,29 @@ +var default_callbacks = { + 'nodejs': 'http://localhost:CHANGE-TO-YOUR-PORT/callback', + 'rails': 'http://localhost:CHANGE-TO-YOUR-PORT/auth/auth0/callback', + 'ruby-on-rails': 'http://localhost:CHANGE-TO-YOUR-PORT/auth/auth0/callback', + 'nancyfx': 'http://localhost:CHANGE-TO-YOUR-PORT/login-callback', + 'laravel': 'http://localhost:CHANGE-TO-YOUR-PORT/auth0/callback', + 'symfony': 'http://localhost:CHANGE-TO-YOUR-PORT/auth0/callback', + 'ios': 'https://{tenant}.auth0.com/mobile', + 'android': 'https://{tenant}.auth0.com/mobile', + 'aspnet': 'http://localhost:CHANGE-TO-YOUR-PORT/LoginCallback.ashx', + 'aspnet-owin': 'http://localhost:CHANGE-TO-YOUR-PORT/signin-auth0', + 'php': 'http://localhost:CHANGE-TO-YOUR-PORT/callback.php', + 'python': 'http://localhost:CHANGE-TO-YOUR-PORT/callback', + 'win8': 'https://{tenant}.auth0.com/mobile', + 'azure': 'http://mysite.azurewebsites.net/LoginCallback.ashx', + 'servicestack': 'http://localhost:CHANGE-TO-YOUR-PORT/api/auth/auth0/', + 'windowsphone': 'https://{tenant}.auth0.com/mobile', + 'xamarin': 'https://{tenant}.auth0.com/mobile', + 'phonegap': 'https://{tenant}.auth0.com/mobile', + 'wpf-winforms': 'https://{tenant}.auth0.com/mobile', + 'spa': 'http://localhost:CHANGE-TO-YOUR-PORT/', + 'angular': 'http://localhost:CHANGE-TO-YOUR-PORT/', + 'java': 'http://localhost:CHANGE-TO-YOUR-PORT/callback' +}; + +exports.get = function (req) { + if (!req.query.backend) return ''; + return default_callbacks[req.query.backend]; +}; \ No newline at end of file diff --git a/lib/extensions.js b/lib/extensions.js new file mode 100644 index 00000000..67b47626 --- /dev/null +++ b/lib/extensions.js @@ -0,0 +1,66 @@ +/** + * Module dependencies. + */ + +var lodash = require('lodash'); + +/** +* Add mixins +*/ +lodash.mixin({ + capitalize : function(string) { + return string.charAt(0).toUpperCase() + string.substring(1); + } +}); + +/** + * Expose extensions + */ + +exports.lodash = lodashExtension; + +/** + * Lodash Pre-compile extension + * + * @param {Object} context + * @return {Function} Showdown extension + * @api public + */ + +function lodashExtension(context) { + // Ugly ugly ugly! + // But it's how showdown works... + return function lodashCompiler(converter) { + return [{ + type: 'lang', + filter: function (text) { + return context.meta.lodash + ? lodash.template(text)(context) + : text; + } + }]; + } +} + + + +exports.warningBlock = warningBlockExtension; + + +function warningBlockExtension(context) { + + return function warningBlockCompiler(converter) { + return [{ + type: 'lang', + filter: function(text) { + return text.replace(/\^\^warning\ (.*)/, function(wholeMatch, m1) { + var result = '
      '; + result += converter.makeHtml(m1); + result += '
      '; + return result; + }); + } + }]; + } + +} diff --git a/lib/external/api-explorer.jade b/lib/external/api-explorer.jade new file mode 100644 index 00000000..9122dcb7 --- /dev/null +++ b/lib/external/api-explorer.jade @@ -0,0 +1,20 @@ +div#api-explorer + +script(src="https://cdn.auth0.com/api-explorer/api-explorer.js", type="text/javascript") +link(rel="stylesheet", href="https://cdn.auth0.com/api-explorer/api-explorer.css", type="text/css") + +script + require(['api-explorer'], function () { + var apiExplorer = require('api-explorer') + apiExplorer({ + el: $('#api-explorer'), + isAuth: #{isAuth}, + tenantDomain: '#{account.namespace}', + clientId: '#{globalClientID}', + clientSecret: '#{globalClientSecret}', + docsDomain: '#{docsDomain}', + user: !{JSON.stringify(user)}, + readOnly: #{readOnly}, + anchors: #{anchors} + }); + }); \ No newline at end of file diff --git a/lib/external/api2-explorer-middleware.js b/lib/external/api2-explorer-middleware.js new file mode 100644 index 00000000..acce80f4 --- /dev/null +++ b/lib/external/api2-explorer-middleware.js @@ -0,0 +1,30 @@ +var fs = require('fs'); +var nconf = require('nconf'); +var xtend = require('xtend'); +var jade = require('jade'); + +var apiTmplPath = __dirname + '/api2-explorer.jade'; + +var apiTmpl = jade.compile(fs.readFileSync(apiTmplPath).toString(), { + filename: apiTmplPath, + pretty: true +}); + +module.exports = function (req, res, next) { + var jadeContext = xtend({}, res.locals, { api2Domain: nconf.get('DOMAIN_URL_API2_EXPLORER') }); + + if (res.locals.account.loggedIn && req.user.is_owner) { + jadeContext.globalClientSecret = res.locals.account.globalClientSecret; + jadeContext.globalClientID = res.locals.account.globalClientId; + + } else { + jadeContext.globalClientSecret = ''; + jadeContext.globalClientID = ''; + } + + res.locals.api2Explorer = function (ctx) { + return apiTmpl(jadeContext); + }; + + next(); +}; \ No newline at end of file diff --git a/lib/external/api2-explorer.jade b/lib/external/api2-explorer.jade new file mode 100644 index 00000000..f4c5624f --- /dev/null +++ b/lib/external/api2-explorer.jade @@ -0,0 +1,26 @@ +div#api2-explorer + +script. + jQuery.browser = jQuery.browser || {}; + (function () { + jQuery.browser.msie = jQuery.browser.msie || false; + jQuery.browser.version = jQuery.browser.version || 0; + if (navigator.userAgent.match(/MSIE ([0-9]+)\./)) { + jQuery.browser.msie = true; + jQuery.browser.version = RegExp.$1; + } + })(); +script(type="text/javascript", src="https://cdn.auth0.com/api2-explorer/api2-explorer.min.js") +script(type="text/javascript", src="https://cdn.auth0.com/api2-explorer/api2-explorer.proxy.js") +link(rel="stylesheet", href="https://cdn.auth0.com/api2-explorer/api2-explorer.css", type="text/css") + +script. + require(['api2-explorer.proxy'], function () { + var api2_explorer = require('api2-explorer.proxy'); + api2_explorer({ + el: $('#api2-explorer'), + clientId: '#{globalClientID}', + clientSecret: '#{globalClientSecret}', + api2Domain: '#{api2Domain}' + }); + }); \ No newline at end of file diff --git a/lib/external/middleware.js b/lib/external/middleware.js new file mode 100644 index 00000000..03c6de34 --- /dev/null +++ b/lib/external/middleware.js @@ -0,0 +1,52 @@ +var fs = require('fs'); +var nconf = require('nconf'); +var xtend = require('xtend'); +var jade = require('jade'); + +var apiTmplPath = __dirname + '/api-explorer.jade'; + +var apiTmpl = jade.compile(fs.readFileSync(apiTmplPath).toString(), { + filename: apiTmplPath, + pretty: true +}); + +module.exports = function (req, res, next) { + // the right-most property takes presedence + var jadeContext = xtend({}, res.locals, { docsDomain: nconf.get('DOMAIN_URL_DOCS') }); + + if (res.locals.account.loggedIn && req.user.is_owner) { + jadeContext.readOnly = false; + jadeContext.user = { + id: req.user.id, + name: req.user.name, + mail: req.user.mail + }; + + jadeContext.globalClientSecret = res.locals.account.globalClientSecret; + jadeContext.globalClientID = res.locals.account.globalClientId; + + } else { + jadeContext.readOnly = true; + jadeContext.user = { + id: 'john.doe', + name: 'John Doe', + mail: 'john@doe.com' + }; + + jadeContext.globalClientSecret = ''; + jadeContext.globalClientID = ''; + } + + res.locals.apiExplorer = function (ctx) { + jadeContext = xtend(jadeContext, ctx); + + // force read-only mode for 'Authentication API' when tenant doesn't have any app + if (!jadeContext.readOnly) { + jadeContext.readOnly = !!(ctx.isAuth && (!res.locals.account.clients || res.locals.account.clients.length === 0)); + } + + return apiTmpl(jadeContext); + }; + + next(); +}; diff --git a/lib/includes/includes.js b/lib/includes/includes.js new file mode 100644 index 00000000..c3524095 --- /dev/null +++ b/lib/includes/includes.js @@ -0,0 +1,17 @@ +var path = require('path'); +var fs = require('fs'); + +var includes = {}; + +module.exports.init = function(p) { + var files = fs.readdirSync(p); + files.forEach(function(file) { + var content = fs.readFileSync(path.join(p, file)); + includes[path.basename(file, '.md')] = content; + }); +} + +module.exports.add = function(req, res, next) { + res.locals.includes = includes; + next(); +} \ No newline at end of file diff --git a/lib/lock-github.js b/lib/lock-github.js new file mode 100644 index 00000000..8bf73aa3 --- /dev/null +++ b/lib/lock-github.js @@ -0,0 +1,22 @@ +/** + * Module dependencies. + */ + +var request = require('request'); +var base = 'https://cdn.rawgit.com/auth0/lock/master/' +module.exports = fetch; + +function fetch(doc, cb) { + + request(base + doc, function (err, response, body) { + if (err) return cb(err); + if (response.statusCode !== 200) return cb(new Error('There was an error fetching ' + base + doc)); + + fetch.cache[doc] = body; + cb(null, fetch.cache[doc]); + + }) + +} + +fetch.cache = {}; diff --git a/lib/middlewares.js b/lib/middlewares.js new file mode 100644 index 00000000..5ce4c81b --- /dev/null +++ b/lib/middlewares.js @@ -0,0 +1,63 @@ +var winston = require('winston'); +var nconf = require('nconf'); +var utils = require('./utils'); + +/** + * Expose middlewares + */ + +exports.configuration = configurationMiddleware; +exports.cors = cors; + +/** + * Parse `configuration` local from `query` parameters + * + * @param {Request} req + * @param {Response} res + * @param {Function} next + * @api public + */ + +function configurationMiddleware (req, res, next) { + // used by lodash extension + var configuration = res.locals.configuration = res.locals.configuration || {}; + + // common data + configuration.frontend = req.query.frontend || null; + configuration.api = req.query.api || null; + configuration.backend = req.query.backend || null; + configuration.mobile = req.query.mobile || null; + // combination data + configuration.thirdParty = req.query['3rd'] || req.query.thirdparty || req.query.thirdpParty || false; + configuration.hybrid = req.query.hybrid || false; + next(); +} + +var ALLOWED_ORIGINS = nconf.get('ALLOWED_ORIGINS'); +var allowedOrigins = ALLOWED_ORIGINS ? ALLOWED_ORIGINS.split(',') : []; + +function cors (req, res, next){ + if (!(allowedOrigins.length && req.headers.origin)) { return next(); } + + // ignore if origin === host + var host = (req.headers['x-forwarded-proto'] || req.protocol) + "://" + req.headers.host; + if (req.headers.origin === host) { + res.set({ + 'Access-Control-Allow-Origin': req.headers.origin, + }); + + return next(); + } + + var isOriginAllowed = allowedOrigins.some(function (o) { + return utils.equalBaseUrls(o, req.headers.origin); + }); + + if (isOriginAllowed){ + res.set({ + 'Access-Control-Allow-Origin': req.headers.origin, + }); + } + + next(); +} \ No newline at end of file diff --git a/lib/packager/index.js b/lib/packager/index.js new file mode 100644 index 00000000..d62be20f --- /dev/null +++ b/lib/packager/index.js @@ -0,0 +1,49 @@ +var _ = require('lodash'); +var nconf = require('nconf'); +var url = require('url'); +var request = require('request'); +var winston = require('winston'); + +module.exports = function (app, authenticatedVarsMiddleware) { + if (nconf.get('PACKAGER_URL')) { + app.get(nconf.get('BASE_URL') + '/:repo/:branch/create-package', authenticatedVarsMiddleware, function(req, res) { + if (req.query.clientId) { + if (!res.locals.account) { + return res.send(401, 'Unauthorized: You need to log in to be able to use a clientId'); + } + + var localClient = _.find(res.locals.account.clients, function(client) { + return client.clientID === req.query.clientId; + }); + + if (!localClient) { + return res.send(401, 'Unauthorized: You can\'t use a clientId that doesn\'t belong to you.'); + } + } + var pkg_url = url.resolve(nconf.get('PACKAGER_URL'), req.url.substr(nconf.get('BASE_URL').length)); + + var pkg_req = request(pkg_url); + + pkg_req.pipe(res); + + pkg_req.on('error', function (err) { + winston.error('error when fetching package', { + error: err.stack, + url: req.originalUrl, + tenant: res.locals && res.locals.account && res.locals.account.tenant + }); + res.send(500); + }); + + res.on('error', function (err) { + winston.error('error when fetching package', { + error: err.stack, + url: req.originalUrl, + tenant: res.locals && res.locals.account && res.locals.account.tenant + }); + }); + + }); + } + +}; diff --git a/lib/quickstart-collections.js b/lib/quickstart-collections.js new file mode 100644 index 00000000..67d334fb --- /dev/null +++ b/lib/quickstart-collections.js @@ -0,0 +1,22 @@ +/** + * Module dependencies. + */ + +exports.apptypes = require('auth0-application-types'); +exports.clientPlatforms = require('auth0-client-platforms'); +exports.nativePlatforms = require('auth0-native-platforms'); +exports.hybridPlatforms = exports.nativePlatforms.filter(hybridFilter); +exports.serverPlatforms = require('auth0-server-platforms'); +exports.serverApis = require('auth0-server-apis'); + +/** + * Filters hybrid platforms out of native platforms + * + * @param {Object} platform + * @return {Boolean} + * @private + */ + +function hybridFilter(platform) { + return !!platform.hybrid +}; diff --git a/lib/quickstart-routes.js b/lib/quickstart-routes.js new file mode 100644 index 00000000..2f0486dd --- /dev/null +++ b/lib/quickstart-routes.js @@ -0,0 +1,55 @@ +/** + * Module dependencies. + */ + +var collections = require('./quickstart-collections'); +var apptypes = collections.apptypes; +var clientPlatforms = collections.clientPlatforms; +var nativePlatforms = collections.nativePlatforms; +var hybridPlatforms = collections.hybridPlatforms; +var serverPlatforms = collections.serverPlatforms; +var serverApis = collections.serverApis; +var debug = require('debug')('docs:quickstart-routes'); + +/** + * Expose routes mapping + */ + +var routes = module.exports = []; + +apptypes.forEach(apptypesComposer); + +function apptypesComposer(application) { + // Base apptype route + var route = '/' + application.name; + routes.push(route); + debug('loaded route %s', route); + + // According to application's type + // compose the rest of + if ('webapp' === application.name) return serverPlatforms.forEach(platformSingleComposer.bind(null, application)); + if ('spa' === application.name) return clientPlatforms.forEach(platformApiComposer.bind(null, application)); + if ('hybrid' === application.name) return hybridPlatforms.forEach(platformApiComposer.bind(null, application)); + if ('native-mobile' === application.name) return nativePlatforms.forEach(platformApiComposer.bind(null, application)); +} + +function platformSingleComposer(application, platform) { + var route = '/' + application.name + '/' + platform.name; + routes.push(route); + debug('loaded route %s', route); +} + +function platformApiComposer(application, platform) { + // Mid way route + platformSingleComposer(application, platform); + // No API route + apiCombinedComposer(application, platform, { name: 'no-api' }) + // API combined route + serverApis.forEach(apiCombinedComposer.bind(null, application, platform)); +} + +function apiCombinedComposer(application, platform, api) { + var route = '/' + application.name + '/' + platform.name + '/' + api.name; + routes.push(route); + debug('loaded route %s', route); +} diff --git a/lib/redirects/index.js b/lib/redirects/index.js new file mode 100644 index 00000000..daaf9a78 --- /dev/null +++ b/lib/redirects/index.js @@ -0,0 +1,26 @@ +var urls = require('./redirect-urls'); +var _ = require('lodash'); +var nconf = require('nconf'); + +module.exports = function (app) { + _.each(urls, function(urlInfo) { + app.redirect(nconf.get('BASE_URL') + urlInfo.from, nconf.get('BASE_URL') + urlInfo.to, 301); + }); + + app.use(nconf.get('BASE_URL'), function(req, res, next) { + if (!/^\/new/.exec(req.url)) return next(); + var url = req.url.replace(/^\/new/, ''); + res.redirect(301, nconf.get('BASE_URL') + url); + }); + + // 301 for client-platform spa renamed to javascript + app.redirect(nconf.get('BASE_URL') + '/quickstart/spa/spa', '/quickstart/spa/javascript', 301); + app.redirect(nconf.get('BASE_URL') + '/quickstart/spa/spa/:api', '/quickstart/spa/javascript/:api', 301); + + // 301 for apptype web renamed to webapp + app.redirect(nconf.get('BASE_URL') + '/quickstart/web', '/quickstart/webapp', 301); + app.redirect(nconf.get('BASE_URL') + '/quickstart/web/:platform', '/quickstart/webapp/:platform', 301); + + // 301 for server-api ror-api renamed to rails-api + app.redirect(nconf.get('BASE_URL') + '/quickstart/:apptype/:platform/ror-api', '/quickstart/:apptype/:platform/rails-api', 301); +}; diff --git a/lib/redirects/redirect-urls.json b/lib/redirects/redirect-urls.json new file mode 100644 index 00000000..01c42c76 --- /dev/null +++ b/lib/redirects/redirect-urls.json @@ -0,0 +1,142 @@ +[{ + "from": "/ionic-tutorial", + "to" : "/native-platforms/ionic" +}, +{ + "from": "/javaapi-tutorial", + "to" : "/server-apis/java" +}, +{ + "from": "/laravelapi-tutorial", + "to" : "/server-apis/php-laravel" +}, +{ + "from": "/nodeapi-tutorial", + "to" : "/server-apis/nodejs" +}, +{ + "from": "/nodejs-tutorial", + "to" : "/server-platforms/nodejs" +}, +{ + "from": "/phpapi-tutorial", + "to" : "/server-apis/php" +}, +{ + "from": "/pythonapi-tutorial", + "to" : "/server-apis/python" +}, +{ + "from": "/rubyapi-tutorial", + "to" : "/server-apis/rails" +}, +{ + "from": "/awsapi-tutorial", + "to": "/server-apis/aws" +}, +{ + "from": "/wams", + "to": "/server-apis/azure-mobile-services" +}, +{ + "from": "/firebaseapi-tutorial", + "to": "/server-apis/firebase" +}, +{ + "from": "/salesforcesandboxapi-tutorial", + "to": "/server-apis/salesforce-sandbox" +}, +{ + "from": "/salesforceapi-tutorial", + "to": "/server-apis/salesforce" +}, +{ + "from": "/sapapi-tutorial", + "to": "/server-apis/sap-odata" +}, +{ + "from": "/rails-tutorial", + "to": "/server-platforms/rails" +}, +{ + "from": "/python-tutorial", + "to": "/server-platforms/python" +}, +{ + "from": "/php-tutorial", + "to": "/server-platforms/php" +}, +{ + "from": "/java-tutorial", + "to" : "/server-platforms/java" +}, +{ + "from": "/phonegap-tutorial", + "to" : "/native-platforms/phonegap" +}, +{ + "from": "/widget", + "to": "/login-widget2" +}, +{ + "from": "/scenarios-keenio", + "to": "/scenarios/keenio" +}, +{ + "from": "/scenarios-mixpanel-fullcontact-salesforce", + "to": "/scenarios/mixpanel-fullcontact-salesforce" +}, +{ + "from": "/scenarios-mqtt", + "to": "/scenarios/mqtt" +}, +{ + "from": "/scenarios-parse", + "to": "/scenarios/parse" +}, +{ + "from": "/scenarios-rapleaf-salesforce", + "to": "/scenarios/rapleaf-salesforce" +}, +{ + "from": "/scenarios-segmentio", + "to": "/scenarios/segmentio" +}, +{ + "from": "/scenarios-splunk", + "to": "/scenarios/splunk" +}, +{ + "from": "/scenarios-tessel", + "to": "/scenarios/tessel" +}, +{ + "from": "/scenarios-unbounce", + "to": "/scenarios/unbounce" +}, +{ + "from": "/updating-appliance", + "to": "/appliance/update" +}, +{ + "from": "/adldap-auth", + "to": "/connector/install" +}, { + "from": "/adldap-x", + "to": "/connector/install-other-platforms" +}, { + "from": "/aspnet-tutorial", + "to": "/server-platforms/aspnet" +}, { + "from": "/aspnet-owin-tutorial", + "to": "/server-platforms/aspnet-owin" +}, { + "from": "/servicestack-tutorial", + "to": "/server-platforms/servicestack" +}, { + "from": "/laravel-tutorial", + "to": "/server-platforms/laravel" +}, { + "from": "/symfony-tutorial", + "to": "/server-platforms/symfony" + }] diff --git a/lib/sdk-snippets/lock/browser.jade b/lib/sdk-snippets/lock/browser.jade new file mode 100644 index 00000000..7f822506 --- /dev/null +++ b/lib/sdk-snippets/lock/browser.jade @@ -0,0 +1,28 @@ +p This is how it will look on a browser... +.browser + .browser-toolbar + .browser-icons + a(href="javascript:history.back()", target="widget-demo") + i.icon-budicon-521 + a(href="javascript:history.forward()", target="widget-demo") + i.icon-budicon-519 + a(href="javascript:document.getElementById('widget-demo').contentWindow.location.reload(true);") + i.icon-budicon-436 + .browser-address + i.icon-budicon-285 + .browser-content(class="login-widget2") + iframe#widget-demo(src='') + + +script + function refresh (method, clientId) { + $('#widget-demo').attr('src', '#{DOMAIN_URL_DOCS}/lock-demos/' + method + '?' + (clientId !== 'YOUR_CLIENT_ID' ? '&a=' + clientId : '') + '&callbackOnHash=#{callbackOnHashMode}'); + } + + $('#client-chooser').change(function (e) { + var method = $('#widget-chooser').val(); + var clientId = $(this).val(); + refresh(method, clientId); + }); + + refresh('login', '#{account.clientId}'); diff --git a/lib/sdk-snippets/lock/demos-routes.js b/lib/sdk-snippets/lock/demos-routes.js new file mode 100644 index 00000000..67d4bc44 --- /dev/null +++ b/lib/sdk-snippets/lock/demos-routes.js @@ -0,0 +1,48 @@ +var widget_script_url = require('./widget_script_url'); +var nconf = require('nconf'); +var getDb = require('../../data'); + +var WIDGET_FALLBACK_CLIENTID = nconf.get('WIDGET_FALLBACK_CLIENTID'); +var DOMAIN_URL_DOCS = nconf.get('DOMAIN_URL_DOCS'); + +module.exports = function (app) { + ['custom', 'embedded', 'link', 'login', 'redirect'].forEach(function (demo) { + app.get(nconf.get('BASE_URL') + '/lock-demos/' + demo, function (req, res, next) { + + widget_script_url.get(req.query.a || WIDGET_FALLBACK_CLIENTID, function (err, widgetUrl, assetsUrl, tenant_domain, namespace, client, auth0jsUrl) { + if (err) return res.send(err); + + client = client || { + clientID: WIDGET_FALLBACK_CLIENTID, + callback: 'http://YOURAPP.com' + }; + + res.locals.widget_url = widgetUrl; + res.locals.auth0_sdk_assets = assetsUrl; + res.locals.tenant_domain = tenant_domain; + res.locals.namespace = namespace; + res.locals.clientID = client.clientID; + res.locals.callback = client.callback; + res.locals.docs_route = DOMAIN_URL_DOCS; + res.locals.auth0js_url = auth0jsUrl; + res.locals.callbackOnHash = req.query.callbackOnHash === 'true'; + + if (!nconf.get('db')) { + res.locals.connections = []; + next(); + } else { + getDb(function (db) { + db.collection('connections').find({client_id: client.clientID, status: true}).toArray(function(err, connections) { + if (err) return res.send(err); + + res.locals.connections = connections; + next(); + }); + }); + } + }); + }, function (req, res) { + res.render(__dirname + '/demos/' + demo); + }); + }); +}; diff --git a/lib/sdk-snippets/lock/demos/custom.jade b/lib/sdk-snippets/lock/demos/custom.jade new file mode 100644 index 00000000..4fee9419 --- /dev/null +++ b/lib/sdk-snippets/lock/demos/custom.jade @@ -0,0 +1,50 @@ +html + head + body + style(type='text/css') + li { + padding: 10px; + } + + div#messages + ul#my-list + + button.signin-google Sign in with Google + br + button.signin-facebook Sign in with Facebook + br + button.signin-waad Sign in with Windows Azure AD + br + button.signin-etc Sign in with ... + br + p --- or --- + label User + input(type="text", id="username") + br + label Password + input(type="password", id="password") + br + button.signin-db Sign in with Username/Password + + script(src=auth0js_url) + script(src='//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js') + script + if (window.Auth0) { + $('.signin-google').on('click', function() { + alert('this would cause a redirect to google triggered by auth0.login({connection: "google-oauth2"}); ') + }); + $('.signin-facebook').on('click', function() { + alert('this would cause a redirect to facebook triggered by auth0.login({connection: "facebook"}); ') + }); + $('.signin-waad').on('click', function() { + alert('this would cause a redirect to facebook triggered by auth0.login({connection: "tenant.onmicrosoft.com"}); ') + }); + $('.signin-etc').on('click', function() { + alert('you probably get the point, auth0.login({connection: "WHATEVER"}); triggers the login') + }); + $('.signin-db').on('click', function() { + alert('this would cause a user/password validation with a db connection and is triggered by auth0.login({connection: "foo", username: "bar", password: "foobar", ...}); ') + }); + } else { + document.getElementById('messages').innerHTML = 'Cannot load the widget at the moment, try again later.'; + } \ No newline at end of file diff --git a/lib/sdk-snippets/lock/demos/embedded.jade b/lib/sdk-snippets/lock/demos/embedded.jade new file mode 100644 index 00000000..e24288f0 --- /dev/null +++ b/lib/sdk-snippets/lock/demos/embedded.jade @@ -0,0 +1,20 @@ +html + head + body + #root(style='width: 280px; margin: 40px auto; padding: 10px; border-style: dashed; border-width: 1px;') + | embeded area + script(src=widget_url) + script + if (window.Auth0Lock) { + var lock = new Auth0Lock('!{clientID}', '!{namespace}', { + assetsUrl: '!{auth0_sdk_assets}' + }); + + lock.show({ + container: 'root', + callbackURL: '!{callback || 'http://YOUR_APP/callback'}', + responsetType: '!{callbackOnHash ? "token" : "code"}' + }); + } else { + document.getElementById('root').innerHTML = 'Cannot load the widget at the moment, try again later.'; + } diff --git a/sdk/demos/link.jade b/lib/sdk-snippets/lock/demos/link.jade similarity index 100% rename from sdk/demos/link.jade rename to lib/sdk-snippets/lock/demos/link.jade diff --git a/lib/sdk-snippets/lock/demos/login.jade b/lib/sdk-snippets/lock/demos/login.jade new file mode 100644 index 00000000..7f369ab0 --- /dev/null +++ b/lib/sdk-snippets/lock/demos/login.jade @@ -0,0 +1,22 @@ +html + head + body + button(onclick='show()') Login + script(src=widget_url) + script + // define lock + var lock = new Auth0Lock('!{clientID}', '!{namespace}', { + assetsUrl: '!{auth0_sdk_assets}' + }); + + // define show function to invoke lock.show + function show() { + lock.show({ + callbackURL: '!{callback || 'http://YOUR_APP/callback'}', + responsetType: '!{callbackOnHash ? "token" : "code"}', + focusInput: false + }); + } + + // invoke at load + show(); diff --git a/lib/sdk-snippets/lock/demos/redirect.jade b/lib/sdk-snippets/lock/demos/redirect.jade new file mode 100644 index 00000000..bb8ba600 --- /dev/null +++ b/lib/sdk-snippets/lock/demos/redirect.jade @@ -0,0 +1,15 @@ +html + head + body + p Trigger sign in by redirecting to: + ul + - each conn in connections + li connection=#{conn.name} (notice the connection param at the end) + a(href='#{tenant_domain}/authorize?response_type=#{callbackOnHash ? 'token' : 'code'}&scope=openid%20profile&client_id=#{clientID}&redirect_uri=#{callback}&connection=#{conn.name}') + pre. + #{tenant_domain}/authorize? + response_type=#{callbackOnHash ? 'token' : 'code'} + &scope=openid%20profile + &client_id=#{clientID} + &redirect_uri=#{callback} + &connection=#{conn.name} \ No newline at end of file diff --git a/lib/sdk-snippets/lock/index.jade b/lib/sdk-snippets/lock/index.jade new file mode 100644 index 00000000..aa8eef9f --- /dev/null +++ b/lib/sdk-snippets/lock/index.jade @@ -0,0 +1,65 @@ +p There are different ways of integrating Auth0 in your site. Below, some of them with a preview and a code snippet to copy paste. + +if account.loggedIn && account.clients && account.clients.length > 0 && !embedded + div + label Select one of your applications: + select#client-chooser + each client in account.clients + option(value="#{client.clientID}") #{client.name} + +div + label Select how you want to trigger login: + select#widget-chooser + option(value="login") Login Widget - Modal + option(value="embedded") Login Widget - Inside a DIV + option(value="custom") Custom UI + option(value="redirect") Plain Link + + +p This is how it will look on a browser... +.browser + .browser-toolbar + .browser-icons + a(href="javascript:history.back()", target="widget-demo") + i.icon-budicon-521 + a(href="javascript:history.forward()", target="widget-demo") + i.icon-budicon-519 + a(href="javascript:document.getElementById('widget-demo').contentWindow.location.reload(true);") + i.icon-budicon-436 + .browser-address + i.icon-budicon-285 + .browser-content(class="login-widget2") + iframe#widget-demo(src='') + +div#widget-snippet + +p Auth0 JavaScript libraries are Open Source: + | #{" "} + a(href="https://github.com/auth0/lock", target="_new") Auth0Lock + | #{" & "} + a(href="https://github.com/auth0/auth0.js", target="_new") Auth0.js + + +script + function refresh (method, clientId) { + $('#widget-demo').attr('src', '#{DOMAIN_URL_DOCS}/lock-demos/' + method + '?' + (clientId !== 'YOUR_CLIENT_ID' ? '&a=' + clientId : '') + '&callbackOnHash=#{callbackOnHashMode}' + '&backend=#{backend}'); + + $('#widget-snippet').load('#{DOMAIN_URL_DOCS}/lock-snippets/' + method + '?' + (clientId !== 'YOUR_CLIENT_ID' ? '&a=' + clientId : '') + '&callbackOnHash=#{callbackOnHashMode}' + '&backend=#{backend}', function() { + prettyPrint(); + }); + } + + $('#widget-chooser').change(function (e) { + var method = $(this).val(); + var clientId = $('#client-chooser').length === 0 ? '#{account.clientId}' : ($('#client-chooser').val() || ''); + refresh(method, clientId); + }); + + $('#client-chooser').change(function (e) { + var method = $('#widget-chooser').val(); + var clientId = $(this).val(); + refresh(method, clientId); + }); + + $('#widget-chooser').val($('#widget-chooser option:first').attr('value')); + $('#widget-chooser').change(); diff --git a/lib/sdk-snippets/lock/middleware-browser.js b/lib/sdk-snippets/lock/middleware-browser.js new file mode 100644 index 00000000..d5983555 --- /dev/null +++ b/lib/sdk-snippets/lock/middleware-browser.js @@ -0,0 +1,34 @@ +var jade = require('jade'); +var fs = require('fs'); +var nconf = require('nconf'); +var tmplPath = __dirname + '/browser.jade'; +var widget_script_url = require('./widget_script_url'); + +var tmpl = jade.compile(fs.readFileSync(tmplPath).toString(), { + filename: tmplPath, + pretty: true +}); + +module.exports = function (req, res, next) { + if (process.env.NODE_ENV !== "production") { + tmpl = jade.compile(fs.readFileSync(tmplPath).toString(), { + filename: tmplPath, + pretty: process.env.NODE_ENV !== "production" + }); + } + + widget_script_url.get(res.locals.account.clientId, function (err, url) { + var jadelocals = { callbackOnHashMode: false }; + jadelocals.widget_url = url; + + Object.keys(res.locals).forEach(function (k) { + jadelocals[k] = res.locals[k]; + }); + + jadelocals.DOMAIN_URL_DOCS = nconf.get('DOMAIN_URL_DOCS'); + + res.locals.browser = tmpl(jadelocals); + + next(); + }); +}; diff --git a/lib/sdk-snippets/lock/middleware.js b/lib/sdk-snippets/lock/middleware.js new file mode 100644 index 00000000..a2ac2ad2 --- /dev/null +++ b/lib/sdk-snippets/lock/middleware.js @@ -0,0 +1,38 @@ +var jade = require('jade'); +var fs = require('fs'); +var nconf = require('nconf'); +var xtend = require('xtend'); +var tmplPath = __dirname + '/index.jade'; +var widget_script_url = require('./widget_script_url'); + +var tmpl = jade.compile(fs.readFileSync(tmplPath).toString(), { + filename: tmplPath, + pretty: true +}); + +module.exports = function (req, res, next) { + if (process.env.NODE_ENV !== "production") { + tmpl = jade.compile(fs.readFileSync(tmplPath).toString(), { + filename: tmplPath, + pretty: process.env.NODE_ENV !== "production" + }); + } + + widget_script_url.get(res.locals.account.clientId, function (err, url) { + var jadelocals = { callbackOnHashMode: false }; + jadelocals.widget_url = url; + + Object.keys(res.locals).forEach(function (k) { + jadelocals[k] = res.locals[k]; + }); + + jadelocals.DOMAIN_URL_DOCS = nconf.get('DOMAIN_URL_DOCS'); + + jadelocals.backend = req.query.backend; + + res.locals.lockSDK = tmpl(jadelocals); + res.locals.lockSDKWithCallbackOnHash = tmpl(xtend(jadelocals, { callbackOnHashMode: true })); + + next(); + }); +}; diff --git a/lib/sdk-snippets/lock/snippets-routes.js b/lib/sdk-snippets/lock/snippets-routes.js new file mode 100644 index 00000000..44b3999d --- /dev/null +++ b/lib/sdk-snippets/lock/snippets-routes.js @@ -0,0 +1,58 @@ +var ejs = require('ejs'); +var fs = require('fs'); +var nconf = require('nconf'); +var default_callback = require('../../default_callback'); +var snippetsPath = __dirname + '/snippets/'; +var readdir = fs.readdirSync; +var read = fs.readFileSync; + + +var widget_script_url = require('./widget_script_url'); + +var snippets_templates = readdir(snippetsPath) + .map(function (fi) { + return { + id: fi.replace(/\.html$/, ''), + tmpl: ejs.compile(read(__dirname + '/snippets/' + fi, { encoding: 'utf8' })) + }; + }); + +var WIDGET_FALLBACK_CLIENTID = nconf.get('WIDGET_FALLBACK_CLIENTID'); + +function include_snippet (locals) { + return function ( snippet_id ) { + return snippets_templates.filter(function (sn) { + return sn.id == snippet_id; + })[0].tmpl(locals); + }; +} + +module.exports = function (app) { + ['custom', 'embedded', 'link', 'login', 'redirect'].forEach(function (snippet) { + app.get(nconf.get('BASE_URL') + '/lock-snippets/' + snippet, function (req, res) { + widget_script_url.get(req.query.a || WIDGET_FALLBACK_CLIENTID, function (err, widgetUrl, assetsUrl, tenant_domain, namespace, client, auth0jsUrl) { + + client = client || { + clientID: WIDGET_FALLBACK_CLIENTID + }; + + var jadelocals = { + widget_url: widgetUrl, + auth0js_url: auth0jsUrl, + callbackOnHash: req.query.callbackOnHash === 'true', + account: { + namespace: namespace, + clientId: client.clientID, + callback: client.callback || default_callback.get(req) + } + }; + + res.locals.include_snippet = include_snippet(jadelocals); + res.locals.env = { + BASE_URL: nconf.get('BASE_URL') + }; + res.render(snippetsPath + snippet); + }); + }); + }); +}; diff --git a/lib/sdk-snippets/lock/snippets/custom.html b/lib/sdk-snippets/lock/snippets/custom.html new file mode 100644 index 00000000..5a595b34 --- /dev/null +++ b/lib/sdk-snippets/lock/snippets/custom.html @@ -0,0 +1,63 @@ +
      +
      +

      --- or ---

      +
      +
      + + + + + \ No newline at end of file diff --git a/lib/sdk-snippets/lock/snippets/custom.jade b/lib/sdk-snippets/lock/snippets/custom.jade new file mode 100644 index 00000000..60eb3e6c --- /dev/null +++ b/lib/sdk-snippets/lock/snippets/custom.jade @@ -0,0 +1,12 @@ +mixin fiddletize(snippet) + div + | Want to play around with this snippet? + form(action="https://codepen.io/pen/define", method='post', target="_blank", style="display: inline;") + input(type="hidden", name="data", value='{"html":#{JSON.stringify(snippet)}}') + input.edit-fiddle(type="submit", value="") + +div#widget-custom.widget-tut() + .snippet-header Copy this HTML/JS code on your website... + pre(class="prettyprint") + code #{include_snippet('custom')} + mixin fiddletize(include_snippet('custom')) \ No newline at end of file diff --git a/lib/sdk-snippets/lock/snippets/embedded.html b/lib/sdk-snippets/lock/snippets/embedded.html new file mode 100644 index 00000000..b07f8f9f --- /dev/null +++ b/lib/sdk-snippets/lock/snippets/embedded.html @@ -0,0 +1,23 @@ +
      + embeded area +
      + + diff --git a/lib/sdk-snippets/lock/snippets/embedded.jade b/lib/sdk-snippets/lock/snippets/embedded.jade new file mode 100644 index 00000000..8474b168 --- /dev/null +++ b/lib/sdk-snippets/lock/snippets/embedded.jade @@ -0,0 +1,12 @@ +mixin fiddletize(snippet) + div + | Want to play around with this snippet? + form(action="https://codepen.io/pen/define", method='post', target="_blank", style="display: inline;") + input(type="hidden", name="data", value='{"html":#{JSON.stringify(snippet)}}') + input.edit-fiddle(type="submit", value="") + +div#widget-embedded.widget-tut() + .snippet-header Copy this HTML/JS code on your website... + pre(class="prettyprint") + code #{include_snippet('embedded')} + mixin fiddletize(include_snippet('embedded')) \ No newline at end of file diff --git a/lib/sdk-snippets/lock/snippets/link.html b/lib/sdk-snippets/lock/snippets/link.html new file mode 100644 index 00000000..7372b4f5 --- /dev/null +++ b/lib/sdk-snippets/lock/snippets/link.html @@ -0,0 +1 @@ +Sign In by redirecting to this link: https://<%= account.namespace %>/login?client=<%= account.clientId %>&response_type=<%= callbackOnHash ? 'token' : 'code' %>&scope=openid%20profile \ No newline at end of file diff --git a/lib/sdk-snippets/lock/snippets/link.jade b/lib/sdk-snippets/lock/snippets/link.jade new file mode 100644 index 00000000..c24f2800 --- /dev/null +++ b/lib/sdk-snippets/lock/snippets/link.jade @@ -0,0 +1,12 @@ +mixin fiddletize(snippet) + div + | Want to play around with this snippet? + form(action="https://codepen.io/pen/define", method='post', target="_blank", style="display: inline;") + input(type="hidden", name="data", value='{"html":#{JSON.stringify(snippet)}}') + input.edit-fiddle(type="submit", value="") + +div#widget-link.widget-tut() + .snippet-header Copy this HTML/JS code on your website... + pre(class="prettyprint") + code #{include_snippet('link')} + mixin fiddletize(include_snippet('link')) \ No newline at end of file diff --git a/lib/sdk-snippets/lock/snippets/login.html b/lib/sdk-snippets/lock/snippets/login.html new file mode 100644 index 00000000..71325043 --- /dev/null +++ b/lib/sdk-snippets/lock/snippets/login.html @@ -0,0 +1,22 @@ + + + diff --git a/lib/sdk-snippets/lock/snippets/login.jade b/lib/sdk-snippets/lock/snippets/login.jade new file mode 100644 index 00000000..35e7193d --- /dev/null +++ b/lib/sdk-snippets/lock/snippets/login.jade @@ -0,0 +1,14 @@ +mixin fiddletize(snippet) + div + | Want to play around with this snippet? + form(action="https://codepen.io/pen/define", method='post', target="_blank", style="display: inline;") + input(type="hidden", name="data", value='{"html":#{JSON.stringify(snippet)}}') + input.edit-fiddle(type="submit", value="") + +div#widget-login.widget-tut() + .snippet-header Copy this HTML/JS code on your website... + pre(class="prettyprint") + code #{include_snippet('login')} + mixin fiddletize(include_snippet('login')) + +script(src=env.BASE_URL + '/js/prettify.js') diff --git a/lib/sdk-snippets/lock/snippets/redirect.jade b/lib/sdk-snippets/lock/snippets/redirect.jade new file mode 100644 index 00000000..5ea6845c --- /dev/null +++ b/lib/sdk-snippets/lock/snippets/redirect.jade @@ -0,0 +1 @@ +div#widget-redirect.widget-tut() \ No newline at end of file diff --git a/lib/sdk-snippets/lock/widget_script_url.js b/lib/sdk-snippets/lock/widget_script_url.js new file mode 100644 index 00000000..fc932dc8 --- /dev/null +++ b/lib/sdk-snippets/lock/widget_script_url.js @@ -0,0 +1,23 @@ +var nconf = require('nconf'); +var clients = require('../../clients'); + +var env = nconf.get('NODE_ENV'); +var LOGIN_WIDGET_URL = nconf.get('LOGIN_WIDGET_URL'); +var AUTH0JS_URL = nconf.get('AUTH0JS_URL'); +var DOMAIN_URL_SERVER = nconf.get('DOMAIN_URL_SERVER'); + +exports.get = function (clientID, done) { + clients.findByClientId(clientID, function (err, client) { + if (err) return done(err); + + var namespace = DOMAIN_URL_SERVER.replace('{tenant}', client ? client.tenant : 'YOUR-DOMAIN'); + var tenant_domain = 'https://' + namespace; + var assets_url; + + if (env !== 'production') { + assets_url = tenant_domain + '/'; + } + + done(null, LOGIN_WIDGET_URL, assets_url, tenant_domain, namespace, client, AUTH0JS_URL); + }); +}; diff --git a/sdk/demos-routes.js b/lib/sdk-snippets/login-widget/demos-routes.js similarity index 89% rename from sdk/demos-routes.js rename to lib/sdk-snippets/login-widget/demos-routes.js index 3f647a28..5fdd724e 100644 --- a/sdk/demos-routes.js +++ b/lib/sdk-snippets/login-widget/demos-routes.js @@ -1,13 +1,13 @@ var widget_script_url = require('./widget_script_url'); var nconf = require('nconf'); -var getDb = require('../lib/data'); +var getDb = require('../../data'); var WIDGET_FALLBACK_CLIENTID = nconf.get('WIDGET_FALLBACK_CLIENTID'); var DOMAIN_URL_DOCS = nconf.get('DOMAIN_URL_DOCS'); module.exports = function (app) { ['custom', 'embedded', 'link', 'login', 'logincss', 'redirect'].forEach(function (demo) { - app.get('/widget-demos/' + demo, function (req, res, next) { + app.get(nconf.get('BASE_URL') + '/widget-demos/' + demo, function (req, res, next) { widget_script_url.get(req.query.a || WIDGET_FALLBACK_CLIENTID, function (err, url, tenant_domain, client) { if (err) return res.send(err); @@ -21,15 +21,15 @@ module.exports = function (app) { getDb(function (db) { db.collection('connections').find({client_id: client.clientID, status: true}).toArray(function(err, connections) { if (err) return res.send(err); - + res.locals.connections = connections; next(); }); }); - + }); }, function (req, res) { res.render(__dirname + '/demos/' + demo); }); }); -}; \ No newline at end of file +}; diff --git a/sdk/demos/custom.jade b/lib/sdk-snippets/login-widget/demos/custom.jade similarity index 100% rename from sdk/demos/custom.jade rename to lib/sdk-snippets/login-widget/demos/custom.jade diff --git a/sdk/demos/embedded.jade b/lib/sdk-snippets/login-widget/demos/embedded.jade similarity index 100% rename from sdk/demos/embedded.jade rename to lib/sdk-snippets/login-widget/demos/embedded.jade diff --git a/lib/sdk-snippets/login-widget/demos/link.jade b/lib/sdk-snippets/login-widget/demos/link.jade new file mode 100644 index 00000000..24914315 --- /dev/null +++ b/lib/sdk-snippets/login-widget/demos/link.jade @@ -0,0 +1,5 @@ +html + head + body + p Sign In by redirecting to this link: + a(href=tenant_domain + "/login?client=" + clientID)= tenant_domain + "/login?client=" + clientID \ No newline at end of file diff --git a/sdk/demos/login.jade b/lib/sdk-snippets/login-widget/demos/login.jade similarity index 100% rename from sdk/demos/login.jade rename to lib/sdk-snippets/login-widget/demos/login.jade diff --git a/sdk/demos/logincss.jade b/lib/sdk-snippets/login-widget/demos/logincss.jade similarity index 100% rename from sdk/demos/logincss.jade rename to lib/sdk-snippets/login-widget/demos/logincss.jade diff --git a/sdk/demos/redirect.jade b/lib/sdk-snippets/login-widget/demos/redirect.jade similarity index 100% rename from sdk/demos/redirect.jade rename to lib/sdk-snippets/login-widget/demos/redirect.jade diff --git a/sdk/index.jade b/lib/sdk-snippets/login-widget/index.jade similarity index 78% rename from sdk/index.jade rename to lib/sdk-snippets/login-widget/index.jade index a7df8b25..7688b126 100644 --- a/sdk/index.jade +++ b/lib/sdk-snippets/login-widget/index.jade @@ -1,20 +1,20 @@ p There are different ways of integrating Auth0 in your site. Below, some of them with a preview and a code snippet to copy paste. -if account.loggedIn && !embedded - div +if account.loggedIn && account.clients && account.clients.length > 0 && !embedded + div label Select one of your applications: select#client-chooser each client in account.clients option(value="#{client.clientID}") #{client.name} -div +div label Select the widget mode: select#widget-chooser option(value="login") Login Widget - Modal option(value="embedded") Login Widget - Inside a DIV option(value="redirect") Login Link option(value="logincss") Login Widget - Customized with CSS [paid plans only] - option(value="custom") JavaSricpt API [paid plans only] + option(value="custom") JavaScript API [paid plans only] p This is how it will look on a browser... @@ -34,10 +34,10 @@ p This is how it will look on a browser... mixin fiddletize(snippet) div - | Want to play around with this snippet? - form(action='http://jsfiddle.net/api/post/library/pure/', method='post', target="_blank", style="display: inline;") - input(type="hidden", name="html", value=snippet) - input.edit-fiddle(type="submit", value="Edit in JsFiddle") + | Want to play around with this snippet? + form(action="https://codepen.io/pen/define", method='post', target="_blank", style="display: inline;") + input(type="hidden", name="data", value='{"html":#{JSON.stringify(snippet)}}') + input.edit-fiddle(type="submit", value="") div#widget-login.widget-tut(style="display:none") .snippet-header Copy this HTML/JS code on your website... @@ -75,11 +75,12 @@ p For more information about the login widget check the documentation a(href="/login-widget") here | . -blockquote - p Building a JavaScript / Single Page Application? Consider adding - code response_type=token - | parameter to the script url or the login link. This is a hint for Auth0 to send the response back as a redirect to your site passing the tokens after the hash sign - code https://yoursite#access_token=...&id_token=... +|
      +p Building a JavaScript / Single Page Application? Consider adding + code response_type=token + | parameter to the script url or the login link. This is a hint for Auth0 to send the response back as a redirect to your site passing the tokens after the hash sign + code https://yoursite#access_token=...&id_token=... +|
      script $('#widget-chooser').change(function (e) { diff --git a/sdk/middleware.js b/lib/sdk-snippets/login-widget/middleware.js similarity index 96% rename from sdk/middleware.js rename to lib/sdk-snippets/login-widget/middleware.js index 94fe4c91..1918aef8 100644 --- a/sdk/middleware.js +++ b/lib/sdk-snippets/login-widget/middleware.js @@ -47,7 +47,7 @@ module.exports = function (req, res, next) { jadelocals.include_snippet = include_snippet(jadelocals); - res.locals.sdk = tmpl(jadelocals); + res.locals.widgetSDK = tmpl(jadelocals); next(); }); -}; \ No newline at end of file +}; diff --git a/sdk/snippets/custom.html b/lib/sdk-snippets/login-widget/snippets/custom.html similarity index 100% rename from sdk/snippets/custom.html rename to lib/sdk-snippets/login-widget/snippets/custom.html diff --git a/sdk/snippets/embedded.html b/lib/sdk-snippets/login-widget/snippets/embedded.html similarity index 100% rename from sdk/snippets/embedded.html rename to lib/sdk-snippets/login-widget/snippets/embedded.html diff --git a/sdk/snippets/link.html b/lib/sdk-snippets/login-widget/snippets/link.html similarity index 100% rename from sdk/snippets/link.html rename to lib/sdk-snippets/login-widget/snippets/link.html diff --git a/sdk/snippets/login.html b/lib/sdk-snippets/login-widget/snippets/login.html similarity index 100% rename from sdk/snippets/login.html rename to lib/sdk-snippets/login-widget/snippets/login.html diff --git a/sdk/snippets/logincss.html b/lib/sdk-snippets/login-widget/snippets/logincss.html similarity index 100% rename from sdk/snippets/logincss.html rename to lib/sdk-snippets/login-widget/snippets/logincss.html diff --git a/lib/sdk-snippets/login-widget/widget_script_url.js b/lib/sdk-snippets/login-widget/widget_script_url.js new file mode 100644 index 00000000..ee9fd3c3 --- /dev/null +++ b/lib/sdk-snippets/login-widget/widget_script_url.js @@ -0,0 +1,29 @@ +var nconf = require('nconf'); +var clients = require('../../clients'); + +var env = nconf.get('NODE_ENV'); +var cdn = nconf.get('CDN'); +var disable_cdn = nconf.get('DISABLE_CDN'); +var DOMAIN_URL_SDK = nconf.get('DOMAIN_URL_SDK'); +var DOMAIN_URL_SERVER = nconf.get('DOMAIN_URL_SERVER'); + +exports.get = function (clientID, done) { + clients.findByClientId(clientID, function (err, client) { + if (err) return done(err); + + var tenant_domain = 'https://' + DOMAIN_URL_SERVER.replace('{tenant}', client ? client.tenant : 'YOUR-DOMAIN'); + var sdk_url; + + if (env === 'production' && !disable_cdn) { + if (cdn) { + sdk_url = 'https://' + DOMAIN_URL_SDK + '/auth0.js#client=' + clientID + '&cdn=https://' + cdn; + } else { + sdk_url = 'https://' + DOMAIN_URL_SDK + '/auth0.js#client=' + clientID; + } + } else { + sdk_url = 'https://' + DOMAIN_URL_SDK + '/auth0.js#client=' + clientID + '&cdn=' + tenant_domain + '&assets=' + tenant_domain; + } + + done(null, sdk_url, tenant_domain, client); + }); +}; diff --git a/lib/sdk-snippets/login-widget2/browser.jade b/lib/sdk-snippets/login-widget2/browser.jade new file mode 100644 index 00000000..89a526df --- /dev/null +++ b/lib/sdk-snippets/login-widget2/browser.jade @@ -0,0 +1,28 @@ +p This is how it will look on a browser... +.browser + .browser-toolbar + .browser-icons + a(href="javascript:history.back()", target="widget-demo") + i.icon-budicon-521 + a(href="javascript:history.forward()", target="widget-demo") + i.icon-budicon-519 + a(href="javascript:document.getElementById('widget-demo').contentWindow.location.reload(true);") + i.icon-budicon-436 + .browser-address + i.icon-budicon-285 + .browser-content(class="login-widget2") + iframe#widget-demo(src='') + + +script + function refresh (method, clientId) { + $('#widget-demo').attr('src', '#{DOMAIN_URL_DOCS}/widget2-demos/' + method + '?' + (clientId !== 'YOUR_CLIENT_ID' ? '&a=' + clientId : '') + '&callbackOnHash=#{callbackOnHashMode}'); + } + + $('#client-chooser').change(function (e) { + var method = $('#widget-chooser').val(); + var clientId = $(this).val(); + refresh(method, clientId); + }); + + refresh('login', '#{account.clientId}'); \ No newline at end of file diff --git a/lib/sdk-snippets/login-widget2/demos-routes.js b/lib/sdk-snippets/login-widget2/demos-routes.js new file mode 100644 index 00000000..a1e60c0a --- /dev/null +++ b/lib/sdk-snippets/login-widget2/demos-routes.js @@ -0,0 +1,48 @@ +var widget_script_url = require('./widget_script_url'); +var nconf = require('nconf'); +var getDb = require('../../data'); + +var WIDGET_FALLBACK_CLIENTID = nconf.get('WIDGET_FALLBACK_CLIENTID'); +var DOMAIN_URL_DOCS = nconf.get('DOMAIN_URL_DOCS'); + +module.exports = function (app) { + ['custom', 'embedded', 'link', 'login', 'redirect'].forEach(function (demo) { + app.get(nconf.get('BASE_URL') + '/widget2-demos/' + demo, function (req, res, next) { + + widget_script_url.get(req.query.a || WIDGET_FALLBACK_CLIENTID, function (err, widgetUrl, assetsUrl, tenant_domain, namespace, client, auth0jsUrl) { + if (err) return res.send(err); + + client = client || { + clientID: WIDGET_FALLBACK_CLIENTID, + callback: 'http://YOURAPP.com' + }; + + res.locals.widget_url = widgetUrl; + res.locals.auth0_sdk_assets = assetsUrl; + res.locals.tenant_domain = tenant_domain; + res.locals.namespace = namespace; + res.locals.clientID = client.clientID; + res.locals.callback = client.callback; + res.locals.docs_route = DOMAIN_URL_DOCS; + res.locals.auth0js_url = auth0jsUrl; + res.locals.callbackOnHash = req.query.callbackOnHash === 'true'; + + if (!nconf.get('db')) { + res.locals.connections = []; + next(); + } else { + getDb(function (db) { + db.collection('connections').find({client_id: client.clientID, status: true}).toArray(function(err, connections) { + if (err) return res.send(err); + + res.locals.connections = connections; + next(); + }); + }); + } + }); + }, function (req, res) { + res.render(__dirname + '/demos/' + demo); + }); + }); +}; diff --git a/lib/sdk-snippets/login-widget2/demos/custom.jade b/lib/sdk-snippets/login-widget2/demos/custom.jade new file mode 100644 index 00000000..4fee9419 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/demos/custom.jade @@ -0,0 +1,50 @@ +html + head + body + style(type='text/css') + li { + padding: 10px; + } + + div#messages + ul#my-list + + button.signin-google Sign in with Google + br + button.signin-facebook Sign in with Facebook + br + button.signin-waad Sign in with Windows Azure AD + br + button.signin-etc Sign in with ... + br + p --- or --- + label User + input(type="text", id="username") + br + label Password + input(type="password", id="password") + br + button.signin-db Sign in with Username/Password + + script(src=auth0js_url) + script(src='//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js') + script + if (window.Auth0) { + $('.signin-google').on('click', function() { + alert('this would cause a redirect to google triggered by auth0.login({connection: "google-oauth2"}); ') + }); + $('.signin-facebook').on('click', function() { + alert('this would cause a redirect to facebook triggered by auth0.login({connection: "facebook"}); ') + }); + $('.signin-waad').on('click', function() { + alert('this would cause a redirect to facebook triggered by auth0.login({connection: "tenant.onmicrosoft.com"}); ') + }); + $('.signin-etc').on('click', function() { + alert('you probably get the point, auth0.login({connection: "WHATEVER"}); triggers the login') + }); + $('.signin-db').on('click', function() { + alert('this would cause a user/password validation with a db connection and is triggered by auth0.login({connection: "foo", username: "bar", password: "foobar", ...}); ') + }); + } else { + document.getElementById('messages').innerHTML = 'Cannot load the widget at the moment, try again later.'; + } \ No newline at end of file diff --git a/lib/sdk-snippets/login-widget2/demos/embedded.jade b/lib/sdk-snippets/login-widget2/demos/embedded.jade new file mode 100644 index 00000000..09045685 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/demos/embedded.jade @@ -0,0 +1,19 @@ +html + head + body + #root(style='width: 280px; margin: 40px auto; padding: 10px; border-style: dashed; border-width: 1px;') + | embeded area + script(src=widget_url) + script + if (window.Auth0Widget) { + var widget = new Auth0Widget({ + domain: '!{namespace}', + clientID: '!{clientID}', + callbackURL: '!{callback || 'http://YOUR_APP/callback'}', + assetsUrl: '!{auth0_sdk_assets}', + callbackOnLocationHash: !{callbackOnHash} + }); + widget.signin({ container: 'root', chrome: true, _avoidInitialFocus: true }); + } else { + document.getElementById('root').innerHTML = 'Cannot load the widget at the moment, try again later.'; + } diff --git a/lib/sdk-snippets/login-widget2/demos/link.jade b/lib/sdk-snippets/login-widget2/demos/link.jade new file mode 100644 index 00000000..24914315 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/demos/link.jade @@ -0,0 +1,5 @@ +html + head + body + p Sign In by redirecting to this link: + a(href=tenant_domain + "/login?client=" + clientID)= tenant_domain + "/login?client=" + clientID \ No newline at end of file diff --git a/lib/sdk-snippets/login-widget2/demos/login.jade b/lib/sdk-snippets/login-widget2/demos/login.jade new file mode 100644 index 00000000..19a6de17 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/demos/login.jade @@ -0,0 +1,14 @@ +html + head + body + button(onclick='widget.signin()') Login + script(src=widget_url) + script + var widget = new Auth0Widget({ + domain: '!{namespace}', + clientID: '!{clientID}', + callbackURL: '!{callback || 'http://YOUR_APP/callback'}', + assetsUrl: '!{auth0_sdk_assets}', + callbackOnLocationHash: !{callbackOnHash} + }); + widget.signin({_avoidInitialFocus: true}); \ No newline at end of file diff --git a/lib/sdk-snippets/login-widget2/demos/redirect.jade b/lib/sdk-snippets/login-widget2/demos/redirect.jade new file mode 100644 index 00000000..bb8ba600 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/demos/redirect.jade @@ -0,0 +1,15 @@ +html + head + body + p Trigger sign in by redirecting to: + ul + - each conn in connections + li connection=#{conn.name} (notice the connection param at the end) + a(href='#{tenant_domain}/authorize?response_type=#{callbackOnHash ? 'token' : 'code'}&scope=openid%20profile&client_id=#{clientID}&redirect_uri=#{callback}&connection=#{conn.name}') + pre. + #{tenant_domain}/authorize? + response_type=#{callbackOnHash ? 'token' : 'code'} + &scope=openid%20profile + &client_id=#{clientID} + &redirect_uri=#{callback} + &connection=#{conn.name} \ No newline at end of file diff --git a/lib/sdk-snippets/login-widget2/index.jade b/lib/sdk-snippets/login-widget2/index.jade new file mode 100644 index 00000000..73136d70 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/index.jade @@ -0,0 +1,63 @@ +p There are different ways of integrating Auth0 in your site. Below, some of them with a preview and a code snippet to copy paste. + +if account.loggedIn && account.clients && account.clients.length > 0 && !embedded + div + label Select one of your applications: + select#client-chooser + each client in account.clients + option(value="#{client.clientID}") #{client.name} + +div + label Select how you want to trigger login: + select#widget-chooser + option(value="login") Login Widget - Modal + option(value="embedded") Login Widget - Inside a DIV + option(value="custom") Custom UI + option(value="redirect") Plain Link + + +p This is how it will look on a browser... +.browser + .browser-toolbar + .browser-icons + a(href="javascript:history.back()", target="widget-demo") + i.icon-budicon-521 + a(href="javascript:history.forward()", target="widget-demo") + i.icon-budicon-519 + a(href="javascript:document.getElementById('widget-demo').contentWindow.location.reload(true);") + i.icon-budicon-436 + .browser-address + i.icon-budicon-285 + .browser-content(class="login-widget2") + iframe#widget-demo(src='') + +div#widget-snippet + +p Auth0 JavaScript libraries are Open Source: + a(href="https://github.com/auth0/widget", target="_new") Widget + a(href="https://github.com/auth0/auth0.js", target="_new") Auth0.js + + +script + function refresh (method, clientId) { + $('#widget-demo').attr('src', '#{DOMAIN_URL_DOCS}/widget2-demos/' + method + '?' + (clientId !== 'YOUR_CLIENT_ID' ? '&a=' + clientId : '') + '&callbackOnHash=#{callbackOnHashMode}' + '&backend=#{backend}'); + + $('#widget-snippet').load('#{DOMAIN_URL_DOCS}/widget2-snippets/' + method + '?' + (clientId !== 'YOUR_CLIENT_ID' ? '&a=' + clientId : '') + '&callbackOnHash=#{callbackOnHashMode}' + '&backend=#{backend}', function() { + prettyPrint(); + }); + } + + $('#widget-chooser').change(function (e) { + var method = $(this).val(); + var clientId = $('#client-chooser').length === 0 ? '#{account.clientId}' : ($('#client-chooser').val() || ''); + refresh(method, clientId); + }); + + $('#client-chooser').change(function (e) { + var method = $('#widget-chooser').val(); + var clientId = $(this).val(); + refresh(method, clientId); + }); + + $('#widget-chooser').val($('#widget-chooser option:first').attr('value')); + $('#widget-chooser').change(); diff --git a/lib/sdk-snippets/login-widget2/middleware.js b/lib/sdk-snippets/login-widget2/middleware.js new file mode 100644 index 00000000..73c2dfaa --- /dev/null +++ b/lib/sdk-snippets/login-widget2/middleware.js @@ -0,0 +1,38 @@ +var jade = require('jade'); +var fs = require('fs'); +var nconf = require('nconf'); +var xtend = require('xtend'); +var tmplPath = __dirname + '/index.jade'; +var widget_script_url = require('./widget_script_url'); + +var tmpl = jade.compile(fs.readFileSync(tmplPath).toString(), { + filename: tmplPath, + pretty: true +}); + +module.exports = function (req, res, next) { + if (process.env.NODE_ENV !== "production") { + tmpl = jade.compile(fs.readFileSync(tmplPath).toString(), { + filename: tmplPath, + pretty: process.env.NODE_ENV !== "production" + }); + } + + widget_script_url.get(res.locals.account.clientId, function (err, url) { + var jadelocals = { callbackOnHashMode: false }; + jadelocals.widget_url = url; + + Object.keys(res.locals).forEach(function (k) { + jadelocals[k] = res.locals[k]; + }); + + jadelocals.DOMAIN_URL_DOCS = nconf.get('DOMAIN_URL_DOCS'); + + jadelocals.backend = req.query.backend; + + res.locals.widgetSDK2 = tmpl(jadelocals); + res.locals.widgetSDK2WithCallbackOnHash = tmpl(xtend(jadelocals, { callbackOnHashMode: true })); + + next(); + }); +}; diff --git a/lib/sdk-snippets/login-widget2/snippets-routes.js b/lib/sdk-snippets/login-widget2/snippets-routes.js new file mode 100644 index 00000000..58c049f6 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/snippets-routes.js @@ -0,0 +1,54 @@ +var ejs = require('ejs'); +var fs = require('fs'); +var nconf = require('nconf'); +var default_callback = require('../../default_callback'); + +var widget_script_url = require('./widget_script_url'); + +var snippets_templates = fs.readdirSync(__dirname + '/snippets') + .map(function (fi) { + return { + id: fi.replace(/\.html$/, ''), + tmpl: ejs.compile(fs.readFileSync(__dirname + '/snippets/' + fi).toString()) + }; + }); + +var WIDGET_FALLBACK_CLIENTID = nconf.get('WIDGET_FALLBACK_CLIENTID'); + +function include_snippet (locals) { + return function ( snippet_id ) { + return snippets_templates.filter(function (sn) { + return sn.id == snippet_id; + })[0].tmpl(locals); + }; +} + +module.exports = function (app) { + ['custom', 'embedded', 'link', 'login', 'redirect'].forEach(function (snippet) { + app.get(nconf.get('BASE_URL') + '/widget2-snippets/' + snippet, function (req, res) { + widget_script_url.get(req.query.a || WIDGET_FALLBACK_CLIENTID, function (err, widgetUrl, assetsUrl, tenant_domain, namespace, client, auth0jsUrl) { + + client = client || { + clientID: WIDGET_FALLBACK_CLIENTID + }; + + var jadelocals = { + widget_url: widgetUrl, + auth0js_url: auth0jsUrl, + callbackOnHash: req.query.callbackOnHash === 'true', + account: { + namespace: namespace, + clientId: client.clientID, + callback: client.callback || default_callback.get(req) + } + }; + + res.locals.include_snippet = include_snippet(jadelocals); + res.locals.env = { + BASE_URL: nconf.get('BASE_URL') + }; + res.render(__dirname + '/snippets/' + snippet); + }); + }); + }); +}; diff --git a/lib/sdk-snippets/login-widget2/snippets/custom.html b/lib/sdk-snippets/login-widget2/snippets/custom.html new file mode 100644 index 00000000..5a595b34 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/snippets/custom.html @@ -0,0 +1,63 @@ +
      +
      +

      --- or ---

      +
      +
      + + + + + \ No newline at end of file diff --git a/lib/sdk-snippets/login-widget2/snippets/custom.jade b/lib/sdk-snippets/login-widget2/snippets/custom.jade new file mode 100644 index 00000000..60eb3e6c --- /dev/null +++ b/lib/sdk-snippets/login-widget2/snippets/custom.jade @@ -0,0 +1,12 @@ +mixin fiddletize(snippet) + div + | Want to play around with this snippet? + form(action="https://codepen.io/pen/define", method='post', target="_blank", style="display: inline;") + input(type="hidden", name="data", value='{"html":#{JSON.stringify(snippet)}}') + input.edit-fiddle(type="submit", value="") + +div#widget-custom.widget-tut() + .snippet-header Copy this HTML/JS code on your website... + pre(class="prettyprint") + code #{include_snippet('custom')} + mixin fiddletize(include_snippet('custom')) \ No newline at end of file diff --git a/lib/sdk-snippets/login-widget2/snippets/embedded.html b/lib/sdk-snippets/login-widget2/snippets/embedded.html new file mode 100644 index 00000000..38b11163 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/snippets/embedded.html @@ -0,0 +1,28 @@ +
      + embeded area +
      + + diff --git a/lib/sdk-snippets/login-widget2/snippets/embedded.jade b/lib/sdk-snippets/login-widget2/snippets/embedded.jade new file mode 100644 index 00000000..8474b168 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/snippets/embedded.jade @@ -0,0 +1,12 @@ +mixin fiddletize(snippet) + div + | Want to play around with this snippet? + form(action="https://codepen.io/pen/define", method='post', target="_blank", style="display: inline;") + input(type="hidden", name="data", value='{"html":#{JSON.stringify(snippet)}}') + input.edit-fiddle(type="submit", value="") + +div#widget-embedded.widget-tut() + .snippet-header Copy this HTML/JS code on your website... + pre(class="prettyprint") + code #{include_snippet('embedded')} + mixin fiddletize(include_snippet('embedded')) \ No newline at end of file diff --git a/lib/sdk-snippets/login-widget2/snippets/link.html b/lib/sdk-snippets/login-widget2/snippets/link.html new file mode 100644 index 00000000..7372b4f5 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/snippets/link.html @@ -0,0 +1 @@ +Sign In by redirecting to this link: https://<%= account.namespace %>/login?client=<%= account.clientId %>&response_type=<%= callbackOnHash ? 'token' : 'code' %>&scope=openid%20profile \ No newline at end of file diff --git a/lib/sdk-snippets/login-widget2/snippets/link.jade b/lib/sdk-snippets/login-widget2/snippets/link.jade new file mode 100644 index 00000000..c24f2800 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/snippets/link.jade @@ -0,0 +1,12 @@ +mixin fiddletize(snippet) + div + | Want to play around with this snippet? + form(action="https://codepen.io/pen/define", method='post', target="_blank", style="display: inline;") + input(type="hidden", name="data", value='{"html":#{JSON.stringify(snippet)}}') + input.edit-fiddle(type="submit", value="") + +div#widget-link.widget-tut() + .snippet-header Copy this HTML/JS code on your website... + pre(class="prettyprint") + code #{include_snippet('link')} + mixin fiddletize(include_snippet('link')) \ No newline at end of file diff --git a/lib/sdk-snippets/login-widget2/snippets/login.html b/lib/sdk-snippets/login-widget2/snippets/login.html new file mode 100644 index 00000000..e3e53612 --- /dev/null +++ b/lib/sdk-snippets/login-widget2/snippets/login.html @@ -0,0 +1,24 @@ + + + diff --git a/lib/sdk-snippets/login-widget2/snippets/login.jade b/lib/sdk-snippets/login-widget2/snippets/login.jade new file mode 100644 index 00000000..5539a93e --- /dev/null +++ b/lib/sdk-snippets/login-widget2/snippets/login.jade @@ -0,0 +1,14 @@ +mixin fiddletize(snippet) + div + | Want to play around with this snippet? + form(action="https://codepen.io/pen/define", method='post', style="display: inline;") + input(type="hidden", name="data", value='{"html":#{JSON.stringify(snippet)}}') + input.edit-fiddle(type="submit", ) + +div#widget-login.widget-tut() + .snippet-header Copy this HTML/JS code on your website... + pre(class="prettyprint") + code #{include_snippet('login')} + mixin fiddletize(include_snippet('login')) + +script(src=env.BASE_URL + '/js/prettify.js') diff --git a/lib/sdk-snippets/login-widget2/snippets/redirect.jade b/lib/sdk-snippets/login-widget2/snippets/redirect.jade new file mode 100644 index 00000000..5ea6845c --- /dev/null +++ b/lib/sdk-snippets/login-widget2/snippets/redirect.jade @@ -0,0 +1 @@ +div#widget-redirect.widget-tut() \ No newline at end of file diff --git a/lib/sdk-snippets/login-widget2/widget_script_url.js b/lib/sdk-snippets/login-widget2/widget_script_url.js new file mode 100644 index 00000000..ba9988ad --- /dev/null +++ b/lib/sdk-snippets/login-widget2/widget_script_url.js @@ -0,0 +1,23 @@ +var nconf = require('nconf'); +var clients = require('../../clients'); + +var env = nconf.get('NODE_ENV'); +var LEGACY_WIDGET_URL = nconf.get('LEGACY_WIDGET_URL'); +var AUTH0JS_URL = nconf.get('AUTH0JS_URL'); +var DOMAIN_URL_SERVER = nconf.get('DOMAIN_URL_SERVER'); + +exports.get = function (clientID, done) { + clients.findByClientId(clientID, function (err, client) { + if (err) return done(err); + + var namespace = DOMAIN_URL_SERVER.replace('{tenant}', client ? client.tenant : 'YOUR-DOMAIN'); + var tenant_domain = 'https://' + namespace; + var assets_url; + + if (env !== 'production') { + assets_url = tenant_domain + '/'; + } + + done(null, LEGACY_WIDGET_URL, assets_url, tenant_domain, namespace, client, AUTH0JS_URL); + }); +}; diff --git a/lib/sessionStore.js b/lib/sessionStore.js index 31cd75b5..c5fab91d 100644 --- a/lib/sessionStore.js +++ b/lib/sessionStore.js @@ -1,7 +1,15 @@ -var getDb = require('./data'); -var Yams = require('yams'); +var nconf = require('nconf'); + +if (!nconf.get('db')) { + var MemoryStore = require('connect/lib/middleware/session').MemoryStore; + module.exports = new MemoryStore(); + return; +} +var Yams = require('yams'); module.exports = new Yams(function (callback) { + var getDb = require('./data'); + getDb(function (db) { callback(null, db.collection('sessions')); }); diff --git a/lib/set_current_tenant.js b/lib/set_current_tenant.js new file mode 100644 index 00000000..8e11edb0 --- /dev/null +++ b/lib/set_current_tenant.js @@ -0,0 +1,44 @@ +var winston = require('winston'); +var authorizations = require('./authorizations'); + +// - set req.session.current_tenant to switch account +// - delete req.session.current_tenant to switch to default account +// - delete req.session.current_tenants to refresh req.user.tenants list +module.exports = function (req, res, next) { + var done = function () { + req.user.tenants = req.session.current_tenants; + + if (req.user.tenants.length === 0) { + // new user + delete req.user.tenant; + return next(); + } + + var is_valid = function (t) { + return t && req.user.tenants.indexOf(t) > -1; + }; + + var tenant = req.session.current_tenant || req.user.default_tenant; + + req.user.tenant = is_valid(tenant) ? tenant : req.user.tenants[0]; + req.session.current_tenant = req.user.tenant; + + winston.info('Current tenant is: ' + req.user.tenant); + winston.info('Tenants list', req.user.tenants); + + next(); + }; + + if (!req.user) { return next(); } + if (req.session.current_tenants) { return done(); } + + authorizations.getUserTenants(req.user.id, function (err, tenants) { + if (err) { + winston.error('get user tenants error: ' + err.message); + return res.send(500, err.message); + } + + req.session.current_tenants = tenants || []; + done(); + }); +}; diff --git a/lib/set_user_is_owner.js b/lib/set_user_is_owner.js new file mode 100644 index 00000000..c0b1b13b --- /dev/null +++ b/lib/set_user_is_owner.js @@ -0,0 +1,27 @@ +var authorizations = require('./authorizations'); +var winston = require('winston'); + +module.exports = function (req, res, next) { + if (req.user) { + if (!req.user.tenant) return next(); + if (!('tenant_owner' in req.session) || req.session.tenant_owner.tenant !== req.user.tenant) { + return authorizations.getTenantOwners(req.user.tenant, function (err, owners) { + if (err) { + winston.error('authorizations.getTenantOwners', { tenant: req.user.tenant, error: err }); + res.send(500); + } + + req.session.tenant_owner = { + is_owner: owners.some(function (o) { return o.id === req.user.id; } ), + tenant: req.user.tenant + }; + + req.user.is_owner = req.session.tenant_owner.is_owner; + next(); + }); + } else { + req.user.is_owner = req.session.tenant_owner.is_owner; + } + } + next(); +}; diff --git a/lib/setupLogger.js b/lib/setupLogger.js index b365aa7b..8b7e39fc 100644 --- a/lib/setupLogger.js +++ b/lib/setupLogger.js @@ -1,44 +1,24 @@ -var env = process.env; var winston = require("winston"); var url = require('url'); - -winston.setLevels(winston.config.syslog.levels); +var nconf = require('nconf'); winston.remove(winston.transports.Console); -winston.add(winston.transports.Console, { - colorize: true, - level: env["CONSOLE_LOG_LEVEL"], - prettyPrint: true +winston.add(winston.transports.Console, { + colorize: true, + level: nconf.get("CONSOLE_LOG_LEVEL"), + prettyPrint: true }); -if(env['NODE_ENV'] === "production") { - if(env['LOG_TO_WEB_URL']){ - var parsedLogToWebUrl = url.parse(env['LOG_TO_WEB_URL']); - - winston.add(winston.transports.Webhook, { - host: parsedLogToWebUrl.hostname, - port: parseInt(parsedLogToWebUrl.port || 80, 10), - method: 'POST', - path: parsedLogToWebUrl.path, - level: env['LOG_TO_WEB_LEVEL'] || 'error', - handleExceptions: true - }); - } +if(nconf.get('NODE_ENV') === "production" && nconf.get('LOG_TO_WEB_URL')) { + var parsedLogToWebUrl = url.parse(nconf.get('LOG_TO_WEB_URL')); - if(env['LOG_TO_EMAIL_FROM'] && env['LOG_TO_EMAIL_TO']){ - require('winston-email'); - winston.add(winston.transports.Email, { - from : env['LOG_TO_EMAIL_FROM'], - to : env['LOG_TO_EMAIL_TO'], - service: env['LOG_TO_EMAIL_SERVICE'] || 'Gmail', - auth : { - user: env['LOG_TO_EMAIL_USER'], - pass: env['LOG_TO_EMAIL_PASSWORD'] - }, - tags : ['auth0-docs'], - level : env['LOG_TO_EMAIL_LEVEL'] || 'error', - handleExceptions: true - }); - } -} \ No newline at end of file + winston.add(winston.transports.Webhook, { + host: parsedLogToWebUrl.hostname, + port: parseInt(parsedLogToWebUrl.port || 80, 10), + method: 'POST', + path: parsedLogToWebUrl.path, + level: nconf.get('LOG_TO_WEB_LEVEL') || 'error', + handleExceptions: true + }); +} diff --git a/lib/sitemap/index.js b/lib/sitemap/index.js new file mode 100644 index 00000000..87cbe2be --- /dev/null +++ b/lib/sitemap/index.js @@ -0,0 +1,81 @@ +/** + * Module dependencies. + */ + +var fs = require('fs'); +var ejs = require('ejs'); +var lsr = require('lsr'); +var path = require('path'); +var resolve = path.resolve; +var read = fs.readFileSync; +var qsroutes = require('../quickstart-routes'); +var tmplPath = __dirname + '/sitemap.xml.ejs'; +var tmplString = read(tmplPath, 'utf8'); +var sitemap = ejs.compile(tmplString); +var debug = require('debug')('docs:sitemap'); +var nconf = require('nconf'); + +var urls = []; + +/** + * List all /docs documents urls in sitemap + * discarding the ones already in sitemap list + */ + +var docspath = resolve(__dirname, '../../docs'); +var Doc = require('markdocs/lib/markdocs/doc'); +var mockapp = { + getDocsPath: function() { + return docspath; + } +}; + +function pathsFilter(relpath) { + // avoid ./includes/ in sitemap.xml + if (/^\.\/includes/.test(relpath)) { + return false + }; + + return true; +} + +lsr +.sync(docspath, { filterPath: pathsFilter }) +.forEach(function(fileStat) { + var filepath = fileStat.path; + + // skip if not markdown document + if (!/\.md$/.test(filepath)) return; + + var doc = new Doc(mockapp, filepath); + + // skip if private document + if (!doc.isPublic()) return;; + + // skip if already on the list + if (~urls.indexOf(doc.getUrl())) return; + + debug('adding %s', doc.getUrl()); + urls.push(doc.getUrl()); +}); + +/** + * Add quickstart routes + */ + +qsroutes.forEach(function(r) { + var url = '/quickstart' + r; + debug('adding %s', url); + urls.push(url); +}); + +/** + * Export `Express` app function wrapper + */ + +module.exports = function (app) { + app.get(nconf.get('BASE_URL') + '/sitemap.xml', function (req, res) { + res.set('Content-Type', 'application/xml'); + res.send(sitemap({ urls: urls })); + }); +}; diff --git a/lib/sitemap/sitemap.xml.ejs b/lib/sitemap/sitemap.xml.ejs new file mode 100644 index 00000000..c099438a --- /dev/null +++ b/lib/sitemap/sitemap.xml.ejs @@ -0,0 +1,12 @@ + + + <% urls.forEach(function (url) { %> + <% if (url.indexOf('/') === 0) { %> + + https://auth0.com/docs<%= url %> + <%= url === '/' ? '1.0' : '0.9' %> + weekly + + <% } %> + <% }); %> + diff --git a/lib/test-ping.js b/lib/test-ping.js new file mode 100644 index 00000000..56f930f5 --- /dev/null +++ b/lib/test-ping.js @@ -0,0 +1,29 @@ +var nconf = require('nconf'); +var getDb = require('./data'); +var winston = require('winston'); + +module.exports = function (req, res) { + var result = process.memoryUsage(); + + if (!nconf.get('db')) { + return res.json(200, result); + } + + var ping_check = setTimeout(function () { + winston.error('cant connect to the database (/test mongo ping timedout)'); + res.send(500, 'cann\'t connect to the database'); + process.exit(1); + }, 30000); + + getDb(function (db) { + db.command({ping: 1}, function (err) { + clearTimeout(ping_check); + if (err) { + winston.error('cant connect to the database (/test)'); + res.send(500, 'cann\'t connect to the database'); + } + result['db status'] = 'ok'; + return res.json(200, result); + }); + }); +}; \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 00000000..6a0e93c9 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,96 @@ +var utils = module.exports; + +var crypto = require('crypto'); +var nconf = require('nconf'); +var url = require('url'); + +var constant_time_compare = function (val1, val2) { + if (val1.length !== val2.length) { return false; } + var sentinel; + for (var i = 0; i <= (val1.length - 1); i++) { + sentinel |= val1.charCodeAt(i) ^ val2.charCodeAt(i); + } + return sentinel === 0; +}; + +var decryptAesSha256V2 = function (key, cipher_text) { + var cipher_blob = cipher_text.split('$'); // version $ cipher_text $ iv $ hmac + if (cipher_blob.length !== 4) { + throw new Error('Malformed encrypted blob'); + } + + var ct = cipher_blob[1]; + var iv = new Buffer(cipher_blob[2], 'hex'); + var hmac = cipher_blob[3]; + + var chmac = crypto.createHmac('sha256', nconf.get('HMAC_ENCRYPTION_KEY')); + chmac.update(ct); + chmac.update(iv.toString('hex')); + + if (!constant_time_compare(chmac.digest('hex'), hmac)) { + throw new Error('Encrypted Blob has been tampered'); + } + + var key_hash = crypto.createHash('md5').update(key).digest('hex'); // we need a 32-byte key + + try { + var decryptor = crypto.createDecipheriv('aes-256-cbc', key_hash, iv); + var decrypted = decryptor.update(ct, 'hex', 'utf8'); + decrypted += decryptor.final('utf8'); + return decrypted; + } catch (e) { + throw e; + } +}; + +var decryptAesSha256V1 = function (key, cipher_text, ignoreErrorIfTextIsNotEncrypted) { + try { + var decryptor = crypto.createDecipher('aes-256-cbc', key); + var decrypted = decryptor.update(cipher_text, 'hex', 'utf8'); + decrypted += decryptor.final('utf8'); + return decrypted; + } catch (e) { + // decryptor will throws TypeError when specified text is not encrypted + if (e.name !== 'TypeError' || !ignoreErrorIfTextIsNotEncrypted) { + throw e; + } + } + + return cipher_text; +}; + +utils.decryptAesSha256 = function (key, cipher_text, ignoreErrorIfTextIsNotEncrypted) { + var cipher_blob = cipher_text.split('$'); // version $ cipher_text $ iv $ hmac + var version = cipher_blob[0]; + + switch (version) { + case '2.0': + return decryptAesSha256V2(key, cipher_text); + default: + return decryptAesSha256V1(key, cipher_text, ignoreErrorIfTextIsNotEncrypted); + } +}; + +function getSubdomain(wildcard) { + if (wildcard.split('.').length < 3) { return wildcard; } + var matches = wildcard.match(/^(https?:\/\/)[\w|\*\-]+\.?(.*)/); + if (matches === null) { return wildcard; } + return matches[1] + matches[2]; +} + +utils.equalBaseUrls = function (url1, url2) { + if (!url1 || !url2) + return false; + + if (url1.indexOf('*') > -1 || url2.indexOf('*') > -1) { + url1 = getSubdomain(url1); + url2 = getSubdomain(url2); + } + + var u1 = url.parse(url1); + var u2 = url.parse(url2); + + return u1.host === u2.host && + u1.protocol === u2.protocol; +}; + diff --git a/master.js b/master.js new file mode 100644 index 00000000..182fa7ae --- /dev/null +++ b/master.js @@ -0,0 +1,63 @@ +var cluster = require('cluster'); +var fs = require('fs'); +var crypto = require('crypto'); +var cwd = process.cwd(); + +function getSHA1 () { + return crypto.createHash('sha1') + .update(fs.readFileSync(__filename)) + .digest('hex'); +} + +var version = getSHA1(); + +console.log('Starting master process with pid ' + process.pid); + +//fork the first process +cluster.fork(process.env); + +function reload () { + process.chdir(cwd); + + if (version !== getSHA1()) { + console.log('master server changed, exiting'); + return process.exit(1); + } + + console.log('reloading...'); + var new_worker = cluster.fork(); + new_worker.once('listening', function () { + //stop all other workers + for(var id in cluster.workers) { + if (id === new_worker.id.toString()) { + continue; + } + cluster.workers[id].process.kill('SIGTERM'); + } + }); +} + +process.on('SIGHUP', function () { + reload(); +}).on('SIGTERM', function () { + for(var id in cluster.workers) { + cluster.workers[id].process.kill('SIGTERM'); + } +}); + +if (process.env.NODE_ENV !== 'production') { + // var watch = require('watch'); + // var path = require('path'); + // var fs = require('fs'); + var watchr = require('watchr'); + + watchr.watch({ + path: __dirname, + listeners: { + change: function(changeType, filePath){ + console.log(changeType, filePath.substr(__dirname.length)); + reload(); + } + } + }); +} \ No newline at end of file diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json new file mode 100644 index 00000000..0c21dc1e --- /dev/null +++ b/npm-shrinkwrap.json @@ -0,0 +1,1775 @@ +{ + "name": "auth0-docs", + "version": "0.0.1", + "npm-shrinkwrap-version": "5.1.0", + "dependencies": { + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz" + }, + "auth0-application-types": { + "version": "0.1.1", + "from": "auth0-application-types@git://github.com/auth0/application-types#fd4d0ba869cfe4d82d636473ca9472c7e51c21f7", + "resolved": "git://github.com/auth0/application-types#fd4d0ba869cfe4d82d636473ca9472c7e51c21f7" + }, + "auth0-client-platforms": { + "version": "0.1.3", + "from": "auth0-client-platforms@git://github.com/auth0/client-platforms#a55e088fd85036ee1cbfa0d66208e737350f6174", + "resolved": "git://github.com/auth0/client-platforms#a55e088fd85036ee1cbfa0d66208e737350f6174" + }, + "auth0-native-platforms": { + "version": "0.1.5", + "from": "auth0-native-platforms@git://github.com/auth0/native-platforms#661392aff2e6d361e2e73352551105d74ddba4d3", + "resolved": "git://github.com/auth0/native-platforms#661392aff2e6d361e2e73352551105d74ddba4d3" + }, + "auth0-server-apis": { + "version": "0.1.12", + "from": "auth0-server-apis@git://github.com/auth0/server-apis#7eb6256fd2f9544a58a4f9dca9f23510dc032554", + "resolved": "git://github.com/auth0/server-apis#7eb6256fd2f9544a58a4f9dca9f23510dc032554" + }, + "auth0-server-platforms": { + "version": "0.1.13", + "from": "auth0-server-platforms@git://github.com/auth0/server-platforms#f561d1cc013f63d49457e07dbe57d756e88caf86", + "resolved": "git://github.com/auth0/server-platforms#f561d1cc013f63d49457e07dbe57d756e88caf86" + }, + "connect": { + "version": "2.28.3", + "resolved": "https://registry.npmjs.org/connect/-/connect-2.28.3.tgz", + "dependencies": { + "basic-auth-connect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz" + }, + "body-parser": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.10.2.tgz", + "dependencies": { + "iconv-lite": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.6.tgz" + }, + "on-finished": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.2.0.tgz", + "dependencies": { + "ee-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz" + } + } + }, + "raw-body": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.3.2.tgz" + } + } + }, + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz" + }, + "compression": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.3.1.tgz", + "dependencies": { + "accepts": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.4.tgz", + "dependencies": { + "mime-types": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.9.tgz", + "dependencies": { + "mime-db": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.7.0.tgz" + } + } + }, + "negotiator": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.1.tgz" + } + } + }, + "compressible": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.2.tgz", + "dependencies": { + "mime-db": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.7.0.tgz" + } + } + }, + "vary": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.0.tgz" + } + } + }, + "connect-timeout": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/connect-timeout/-/connect-timeout-1.5.0.tgz", + "dependencies": { + "ms": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.0.tgz" + } + } + }, + "cookie": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.2.tgz" + }, + "cookie-parser": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.4.tgz", + "dependencies": { + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + } + } + }, + "cookie-signature": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.5.tgz" + }, + "csurf": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.6.6.tgz", + "dependencies": { + "csrf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-2.0.6.tgz", + "dependencies": { + "base64-url": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz" + }, + "rndm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.1.0.tgz" + }, + "scmp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-1.0.0.tgz" + }, + "uid-safe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-1.1.0.tgz", + "dependencies": { + "native-or-bluebird": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.1.2.tgz" + } + } + } + } + } + } + }, + "depd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.0.tgz" + }, + "errorhandler": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.3.4.tgz", + "dependencies": { + "accepts": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.4.tgz", + "dependencies": { + "mime-types": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.9.tgz", + "dependencies": { + "mime-db": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.7.0.tgz" + } + } + }, + "negotiator": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.1.tgz" + } + } + }, + "escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz" + } + } + }, + "express-session": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.10.2.tgz", + "dependencies": { + "crc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.2.1.tgz" + }, + "uid-safe": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-1.0.3.tgz", + "dependencies": { + "base64-url": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.0.tgz" + }, + "native-or-bluebird": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.1.2.tgz" + } + } + } + } + }, + "finalhandler": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.3.3.tgz", + "dependencies": { + "escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz" + }, + "on-finished": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.2.0.tgz", + "dependencies": { + "ee-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz" + } + } + } + } + }, + "fresh": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.4.tgz" + }, + "http-errors": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.2.8.tgz", + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "statuses": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz" + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + }, + "morgan": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.5.1.tgz", + "dependencies": { + "basic-auth": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.0.tgz" + }, + "on-finished": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.2.0.tgz", + "dependencies": { + "ee-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz" + } + } + } + } + }, + "multiparty": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-3.3.2.tgz", + "dependencies": { + "readable-stream": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz", + "dependencies": { + "core-util-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + } + }, + "stream-counter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stream-counter/-/stream-counter-0.2.0.tgz" + } + } + }, + "on-headers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.0.tgz" + }, + "parseurl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.0.tgz" + }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz" + }, + "qs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz" + }, + "response-time": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.2.0.tgz" + }, + "serve-favicon": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.2.0.tgz", + "dependencies": { + "etag": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.5.1.tgz", + "dependencies": { + "crc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.2.1.tgz" + } + } + }, + "ms": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.0.tgz" + } + } + }, + "serve-index": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.6.1.tgz", + "dependencies": { + "accepts": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.4.tgz", + "dependencies": { + "negotiator": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.1.tgz" + } + } + }, + "batch": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.5.2.tgz" + }, + "mime-types": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.9.tgz", + "dependencies": { + "mime-db": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.7.0.tgz" + } + } + } + } + }, + "serve-static": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.8.1.tgz", + "dependencies": { + "escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz" + }, + "send": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.11.1.tgz", + "dependencies": { + "destroy": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz" + }, + "etag": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.5.1.tgz", + "dependencies": { + "crc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.2.1.tgz" + } + } + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz" + }, + "ms": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.0.tgz" + }, + "on-finished": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.2.0.tgz", + "dependencies": { + "ee-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz" + } + } + }, + "range-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.2.tgz" + } + } + } + } + }, + "type-is": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.5.7.tgz", + "dependencies": { + "mime-types": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.9.tgz", + "dependencies": { + "mime-db": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.7.0.tgz" + } + } + } + } + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz" + }, + "vhost": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vhost/-/vhost-3.0.0.tgz" + } + } + }, + "debug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.1.1.tgz", + "dependencies": { + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz" + } + } + }, + "ejs": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-0.8.8.tgz" + }, + "express": { + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-3.19.2.tgz", + "dependencies": { + "basic-auth": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.0.tgz" + }, + "commander": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz" + }, + "content-disposition": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.0.tgz" + }, + "cookie": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.2.tgz" + }, + "cookie-signature": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.5.tgz" + }, + "depd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.0.tgz" + }, + "escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz" + }, + "etag": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.5.1.tgz", + "dependencies": { + "crc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.2.1.tgz" + } + } + }, + "fresh": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.4.tgz" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + }, + "merge-descriptors": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-0.0.2.tgz" + }, + "methods": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.1.tgz" + }, + "mkdirp": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + }, + "parseurl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.0.tgz" + }, + "proxy-addr": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.6.tgz", + "dependencies": { + "forwarded": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz" + }, + "ipaddr.js": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-0.1.8.tgz" + } + } + }, + "range-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.2.tgz" + }, + "send": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.11.1.tgz", + "dependencies": { + "destroy": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz" + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz" + }, + "ms": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.0.tgz" + }, + "on-finished": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.2.0.tgz", + "dependencies": { + "ee-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz" + } + } + } + } + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz" + }, + "vary": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.0.tgz" + } + } + }, + "express-redirect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/express-redirect/-/express-redirect-1.1.1.tgz", + "dependencies": { + "sanitize-arguments": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sanitize-arguments/-/sanitize-arguments-2.0.3.tgz" + } + } + }, + "jade": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.27.7.tgz", + "dependencies": { + "coffee-script": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.4.0.tgz" + }, + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz" + }, + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" + } + } + }, + "lodash": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz" + }, + "lsr": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lsr/-/lsr-1.0.0.tgz", + "dependencies": { + "barrage": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/barrage/-/barrage-0.0.4.tgz", + "dependencies": { + "readable-stream": { + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz", + "dependencies": { + "core-util-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + } + } + } + }, + "promise": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-3.2.0.tgz" + } + } + }, + "markdocs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/markdocs/-/markdocs-1.0.1.tgz", + "dependencies": { + "commander": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/commander/-/commander-1.0.5.tgz", + "dependencies": { + "keypress": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz" + } + } + }, + "express": { + "version": "3.18.6", + "resolved": "https://registry.npmjs.org/express/-/express-3.18.6.tgz", + "dependencies": { + "basic-auth": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.0.tgz" + }, + "commander": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-1.3.2.tgz", + "dependencies": { + "keypress": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz" + } + } + }, + "connect": { + "version": "2.27.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-2.27.6.tgz", + "dependencies": { + "basic-auth-connect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz" + }, + "body-parser": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.9.3.tgz", + "dependencies": { + "iconv-lite": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.5.tgz" + }, + "on-finished": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.1.1.tgz", + "dependencies": { + "ee-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz" + } + } + }, + "raw-body": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.3.1.tgz" + } + } + }, + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz" + }, + "compression": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.2.2.tgz", + "dependencies": { + "accepts": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.1.4.tgz", + "dependencies": { + "mime-types": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.9.tgz", + "dependencies": { + "mime-db": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.7.0.tgz" + } + } + }, + "negotiator": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.9.tgz" + } + } + }, + "compressible": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.2.tgz", + "dependencies": { + "mime-db": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.7.0.tgz" + } + } + } + } + }, + "connect-timeout": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/connect-timeout/-/connect-timeout-1.4.0.tgz", + "dependencies": { + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz" + } + } + }, + "cookie-parser": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.4.tgz", + "dependencies": { + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + } + } + }, + "csurf": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.6.6.tgz", + "dependencies": { + "csrf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-2.0.6.tgz", + "dependencies": { + "base64-url": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz" + }, + "rndm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.1.0.tgz" + }, + "scmp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-1.0.0.tgz" + }, + "uid-safe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-1.1.0.tgz", + "dependencies": { + "native-or-bluebird": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.1.2.tgz" + } + } + } + } + } + } + }, + "errorhandler": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.2.4.tgz", + "dependencies": { + "accepts": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.1.4.tgz", + "dependencies": { + "mime-types": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.9.tgz", + "dependencies": { + "mime-db": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.7.0.tgz" + } + } + }, + "negotiator": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.9.tgz" + } + } + } + } + }, + "express-session": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.9.3.tgz", + "dependencies": { + "crc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.2.1.tgz" + }, + "uid-safe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-1.0.1.tgz", + "dependencies": { + "base64-url": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz" + }, + "mz": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-1.3.0.tgz", + "dependencies": { + "native-or-bluebird": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz" + }, + "thenify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.1.0.tgz" + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" + } + } + } + } + } + } + }, + "finalhandler": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.3.2.tgz", + "dependencies": { + "on-finished": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.1.1.tgz", + "dependencies": { + "ee-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz" + } + } + } + } + }, + "http-errors": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.2.8.tgz", + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "statuses": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz" + } + } + }, + "morgan": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.4.1.tgz", + "dependencies": { + "on-finished": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.1.1.tgz", + "dependencies": { + "ee-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz" + } + } + } + } + }, + "multiparty": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-3.3.2.tgz", + "dependencies": { + "readable-stream": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz", + "dependencies": { + "core-util-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + } + }, + "stream-counter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stream-counter/-/stream-counter-0.2.0.tgz" + } + } + }, + "on-headers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.0.tgz" + }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz" + }, + "qs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz" + }, + "response-time": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.2.0.tgz" + }, + "serve-favicon": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.1.7.tgz", + "dependencies": { + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz" + } + } + }, + "serve-index": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.5.3.tgz", + "dependencies": { + "accepts": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.1.4.tgz", + "dependencies": { + "negotiator": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.9.tgz" + } + } + }, + "batch": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.5.1.tgz" + }, + "mime-types": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.9.tgz", + "dependencies": { + "mime-db": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.7.0.tgz" + } + } + } + } + }, + "serve-static": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.7.2.tgz" + }, + "type-is": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.5.7.tgz", + "dependencies": { + "mime-types": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.9.tgz", + "dependencies": { + "mime-db": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.7.0.tgz" + } + } + } + } + }, + "vhost": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vhost/-/vhost-3.0.0.tgz" + } + } + }, + "content-disposition": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.0.tgz" + }, + "cookie": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.2.tgz" + }, + "cookie-signature": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.5.tgz" + }, + "depd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.0.tgz" + }, + "escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz" + }, + "etag": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.5.1.tgz", + "dependencies": { + "crc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.2.1.tgz" + } + } + }, + "fresh": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.4.tgz" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + }, + "merge-descriptors": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-0.0.2.tgz" + }, + "methods": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.0.tgz" + }, + "mkdirp": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + }, + "parseurl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.0.tgz" + }, + "proxy-addr": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.6.tgz", + "dependencies": { + "forwarded": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz" + }, + "ipaddr.js": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-0.1.8.tgz" + } + } + }, + "range-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.2.tgz" + }, + "send": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.10.1.tgz", + "dependencies": { + "destroy": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz" + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz" + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz" + }, + "on-finished": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.1.1.tgz", + "dependencies": { + "ee-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz" + } + } + } + } + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz" + }, + "vary": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.0.tgz" + } + } + }, + "less": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/less/-/less-1.3.1.tgz" + }, + "mkdirp": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.4.tgz" + }, + "rel-to-abs": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/rel-to-abs/-/rel-to-abs-0.1.0.tgz" + }, + "showdown": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-0.3.1.tgz" + }, + "wrench": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.3.4.tgz" + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "dependencies": { + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz" + } + } + } + } + }, + "method-override": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-2.3.1.tgz", + "dependencies": { + "methods": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.1.tgz" + }, + "parseurl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.0.tgz" + }, + "vary": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.0.tgz" + } + } + }, + "mongo-getdb": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mongo-getdb/-/mongo-getdb-1.0.1.tgz" + }, + "mongo-heartbeat": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/mongo-heartbeat/-/mongo-heartbeat-0.0.1.tgz", + "dependencies": { + "debug": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz", + "dependencies": { + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz" + } + } + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz" + } + } + }, + "mongodb": { + "version": "1.3.23", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-1.3.23.tgz", + "dependencies": { + "bson": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-0.2.5.tgz" + }, + "kerberos": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/kerberos/-/kerberos-0.0.3.tgz" + } + } + }, + "nconf": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.6.9.tgz", + "dependencies": { + "async": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz" + }, + "ini": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.3.tgz" + }, + "optimist": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.0.tgz", + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" + } + } + } + } + }, + "newrelic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/newrelic/-/newrelic-1.1.1.tgz", + "dependencies": { + "bunyan": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.14.6.tgz" + }, + "continuation-local-storage": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-2.5.2.tgz", + "dependencies": { + "async-listener": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.4.3.tgz" + }, + "shimmer": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-0.9.2.tgz" + } + } + } + } + }, + "passport": { + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.1.18.tgz", + "dependencies": { + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz" + }, + "pkginfo": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.2.3.tgz" + } + } + }, + "prerender-node": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prerender-node/-/prerender-node-1.2.1.tgz", + "dependencies": { + "request": { + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.40.0.tgz", + "dependencies": { + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz" + }, + "forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz" + }, + "form-data": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "dependencies": { + "async": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz" + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "dependencies": { + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz" + } + } + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz" + } + } + }, + "hawk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", + "dependencies": { + "boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz" + }, + "cryptiles": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz" + }, + "hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz" + }, + "sntp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz" + } + } + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "dependencies": { + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz" + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz" + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz" + } + } + }, + "json-stringify-safe": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz" + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz" + }, + "node-uuid": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.2.tgz" + }, + "oauth-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz" + }, + "qs": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-1.0.2.tgz" + }, + "stringstream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.4.tgz" + }, + "tough-cookie": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-0.12.1.tgz", + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" + } + } + }, + "tunnel-agent": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.0.tgz" + } + } + } + } + }, + "request": { + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.53.0.tgz", + "dependencies": { + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz" + }, + "bl": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.4.tgz", + "dependencies": { + "readable-stream": { + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz", + "dependencies": { + "core-util-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + } + } + } + }, + "caseless": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.9.0.tgz" + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "dependencies": { + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz" + } + } + }, + "forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz" + }, + "form-data": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", + "dependencies": { + "async": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz" + } + } + }, + "hawk": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz", + "dependencies": { + "boom": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.6.1.tgz" + }, + "cryptiles": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.4.tgz" + }, + "hoek": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.11.0.tgz" + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + } + } + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "dependencies": { + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz" + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz" + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz" + } + } + }, + "isstream": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.1.tgz" + }, + "json-stringify-safe": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz" + }, + "mime-types": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.9.tgz", + "dependencies": { + "mime-db": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.7.0.tgz" + } + } + }, + "node-uuid": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.2.tgz" + }, + "oauth-sign": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.6.0.tgz" + }, + "qs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz" + }, + "stringstream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.4.tgz" + }, + "tough-cookie": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-0.12.1.tgz", + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" + } + } + }, + "tunnel-agent": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.0.tgz" + } + } + }, + "server-destroy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.0.tgz" + }, + "web-header": { + "version": "0.4.7", + "from": "web-header@git://github.com/auth0/web-header#59525c7e6f30350ac5e9b432e4e4ecd09295b85f", + "resolved": "git://github.com/auth0/web-header#59525c7e6f30350ac5e9b432e4e4ecd09295b85f" + }, + "winston": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-0.6.2.tgz", + "dependencies": { + "async": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz" + }, + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz" + }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz" + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz" + }, + "pkginfo": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.2.3.tgz" + }, + "request": { + "version": "2.9.203", + "resolved": "https://registry.npmjs.org/request/-/request-2.9.203.tgz" + }, + "stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz" + } + } + }, + "winston-email": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/winston-email/-/winston-email-0.0.6.tgz", + "dependencies": { + "nodemailer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-1.3.0.tgz", + "dependencies": { + "buildmail": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buildmail/-/buildmail-1.2.0.tgz", + "dependencies": { + "addressparser": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-0.3.2.tgz" + }, + "libbase64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz" + }, + "libqp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-0.1.1.tgz" + } + } + }, + "hyperquest": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/hyperquest/-/hyperquest-0.3.0.tgz", + "dependencies": { + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz" + }, + "through": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/through/-/through-2.2.7.tgz" + } + } + }, + "libmime": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-0.1.7.tgz", + "dependencies": { + "iconv-lite": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.7.tgz" + }, + "libbase64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz" + }, + "libqp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-0.1.1.tgz" + } + } + }, + "nodemailer-direct-transport": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nodemailer-direct-transport/-/nodemailer-direct-transport-1.0.0.tgz", + "dependencies": { + "smtp-connection": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-0.1.7.tgz" + } + } + }, + "nodemailer-smtp-transport": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/nodemailer-smtp-transport/-/nodemailer-smtp-transport-0.1.13.tgz", + "dependencies": { + "nodemailer-wellknown": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.5.tgz" + }, + "smtp-connection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-1.1.0.tgz" + } + } + } + } + } + } + }, + "xtend": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.0.6.tgz", + "dependencies": { + "is-object": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-0.1.2.tgz" + }, + "object-keys": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.2.0.tgz", + "dependencies": { + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz" + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz" + }, + "is": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/is/-/is-0.2.7.tgz" + } + } + } + } + }, + "yams": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/yams/-/yams-0.0.2.tgz" + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index 0ac89faa..75c26755 100644 --- a/package.json +++ b/package.json @@ -4,27 +4,53 @@ "version": "0.0.1", "author": "Auth10 ", "dependencies": { - "markdocs": "~0.5.1", - "winston": "~0.6.2", + "async": "~0.2.9", + "auth0-application-types": "git://github.com/auth0/application-types#0.1.1", + "auth0-client-platforms": "git://github.com/auth0/client-platforms#0.1.3", + "auth0-native-platforms": "git://github.com/auth0/native-platforms#0.1.5", + "auth0-server-apis": "git://github.com/auth0/server-apis#0.1.12", + "auth0-server-platforms": "git://github.com/auth0/server-platforms#0.1.13", + "connect": "^2.27.6", + "debug": "^2.0.0", + "ejs": "~0.8.4", + "express": "^3.18.6", + "express-redirect": "^1.1.1", + "jade": "~0.27.7", + "lodash": "^2.4.1", + "lsr": "^1.0.0", + "markdocs": "^1.0.1", + "method-override": "^2.3.1", + "mongo-getdb": "~1.0.1", + "mongo-heartbeat": "0.0.1", + "mongodb": "~1.3.15", "nconf": "~0.6.4", - "express": "~3.0.0rc4", + "newrelic": "~1.1.1", "passport": "~0.1.12", - "jade": "~0.27.7", + "prerender-node": "^1.0.6", + "request": "^2.51.0", + "server-destroy": "^1.0.0", + "web-header": "git://github.com/auth0/web-header#0.4.7", + "winston": "~0.6.2", "winston-email": "0.0.6", - "mongodb": "~1.3.15", "xtend": "~2.0.3", - "connect-mongo": "https://github.com/jfromaniello/connect-mongo/tarball/master", - "ejs": "~0.8.4", - "connect": "~2.8.5", - "yams": "0.0.2", - "mongo-getdb": "~1.0.1" + "yams": "0.0.2" }, "main": "app.js", "scripts": { - "start": "node app.js" + "test": "make test", + "start": "node app.js", + "postinstall": "CI=true bower install --allow-root", + "shrinkwrap": "npm-shrinkwrap" }, "engines": { - "node": "0.8.x", + "node": "0.10.24", "npm": "1.1.x" + }, + "devDependencies": { + "bower": "~1.3.5", + "mocha": "^1.20.1", + "npm-shrinkwrap": "^5.1.0", + "nsp": "^0.5.2", + "watchr": "~2.4.12" } } diff --git a/public/img/1364526327_arrow-up-left.png b/public/img/1364526327_arrow-up-left.png deleted file mode 100644 index 99070f5a..00000000 Binary files a/public/img/1364526327_arrow-up-left.png and /dev/null differ diff --git a/public/img/37signals-register-1.png b/public/img/37signals-register-1.png deleted file mode 100644 index 2db4a911..00000000 Binary files a/public/img/37signals-register-1.png and /dev/null differ diff --git a/public/img/37signals-register-2.png b/public/img/37signals-register-2.png deleted file mode 100644 index 2112b7b9..00000000 Binary files a/public/img/37signals-register-2.png and /dev/null differ diff --git a/public/img/37signals-register-3.png.png b/public/img/37signals-register-3.png.png deleted file mode 100644 index 42fb7248..00000000 Binary files a/public/img/37signals-register-3.png.png and /dev/null differ diff --git a/public/img/37signals-register-4.png b/public/img/37signals-register-4.png deleted file mode 100644 index 6b9eefd2..00000000 Binary files a/public/img/37signals-register-4.png and /dev/null differ diff --git a/public/img/adfs-claimrules.png b/public/img/adfs-claimrules.png deleted file mode 100644 index 476cece3..00000000 Binary files a/public/img/adfs-claimrules.png and /dev/null differ diff --git a/public/img/adfs-connection.png b/public/img/adfs-connection.png deleted file mode 100644 index 28a5be8b..00000000 Binary files a/public/img/adfs-connection.png and /dev/null differ diff --git a/public/img/adfs-identifier.png b/public/img/adfs-identifier.png deleted file mode 100644 index ae45ed1e..00000000 Binary files a/public/img/adfs-identifier.png and /dev/null differ diff --git a/public/img/adfs-importmanual.png b/public/img/adfs-importmanual.png deleted file mode 100644 index 0c0bc2af..00000000 Binary files a/public/img/adfs-importmanual.png and /dev/null differ diff --git a/public/img/adfs-script.png b/public/img/adfs-script.png deleted file mode 100644 index 2fb682dd..00000000 Binary files a/public/img/adfs-script.png and /dev/null differ diff --git a/public/img/adfs-sendldap.png b/public/img/adfs-sendldap.png deleted file mode 100644 index 3834fa92..00000000 Binary files a/public/img/adfs-sendldap.png and /dev/null differ diff --git a/public/img/adfs-url.png b/public/img/adfs-url.png deleted file mode 100644 index 3d15b63a..00000000 Binary files a/public/img/adfs-url.png and /dev/null differ diff --git a/public/img/amazon-add-connection.png b/public/img/amazon-add-connection.png deleted file mode 100644 index 31a2ca4b..00000000 Binary files a/public/img/amazon-add-connection.png and /dev/null differ diff --git a/public/img/amazon-login-1.png b/public/img/amazon-login-1.png deleted file mode 100644 index c5675549..00000000 Binary files a/public/img/amazon-login-1.png and /dev/null differ diff --git a/public/img/amazon-register-app.png b/public/img/amazon-register-app.png deleted file mode 100644 index 953c3c20..00000000 Binary files a/public/img/amazon-register-app.png and /dev/null differ diff --git a/public/img/android-tutorial.png b/public/img/android-tutorial.png deleted file mode 100644 index 7b791dee..00000000 Binary files a/public/img/android-tutorial.png and /dev/null differ diff --git a/public/img/android.png b/public/img/android.png deleted file mode 100644 index ec5c1f57..00000000 Binary files a/public/img/android.png and /dev/null differ diff --git a/public/img/auth0-idp-logical.png b/public/img/auth0-idp-logical.png deleted file mode 100644 index 8f215afb..00000000 Binary files a/public/img/auth0-idp-logical.png and /dev/null differ diff --git a/public/img/auth0-idp.png b/public/img/auth0-idp.png deleted file mode 100644 index 343976bd..00000000 Binary files a/public/img/auth0-idp.png and /dev/null differ diff --git a/public/img/box-register-1.png b/public/img/box-register-1.png deleted file mode 100644 index 7e93d27d..00000000 Binary files a/public/img/box-register-1.png and /dev/null differ diff --git a/public/img/box-register-2.png b/public/img/box-register-2.png deleted file mode 100644 index d3ff214f..00000000 Binary files a/public/img/box-register-2.png and /dev/null differ diff --git a/public/img/box-register-3.png b/public/img/box-register-3.png deleted file mode 100644 index 9b9e6e9b..00000000 Binary files a/public/img/box-register-3.png and /dev/null differ diff --git a/public/img/box-register-4.png b/public/img/box-register-4.png deleted file mode 100644 index 5741b132..00000000 Binary files a/public/img/box-register-4.png and /dev/null differ diff --git a/public/img/check.png b/public/img/check.png deleted file mode 100644 index 603a8e3f..00000000 Binary files a/public/img/check.png and /dev/null differ diff --git a/public/img/connection-add-idp-attributes.png b/public/img/connection-add-idp-attributes.png deleted file mode 100644 index e300b847..00000000 Binary files a/public/img/connection-add-idp-attributes.png and /dev/null differ diff --git a/public/img/connection-add-idp-test-r.png b/public/img/connection-add-idp-test-r.png deleted file mode 100644 index be192fdf..00000000 Binary files a/public/img/connection-add-idp-test-r.png and /dev/null differ diff --git a/public/img/connection-add-idp-test.png b/public/img/connection-add-idp-test.png deleted file mode 100644 index eb740653..00000000 Binary files a/public/img/connection-add-idp-test.png and /dev/null differ diff --git a/public/img/connection-add-idp.png b/public/img/connection-add-idp.png deleted file mode 100644 index b50f07ad..00000000 Binary files a/public/img/connection-add-idp.png and /dev/null differ diff --git a/public/img/connection-add.png b/public/img/connection-add.png deleted file mode 100644 index b916b013..00000000 Binary files a/public/img/connection-add.png and /dev/null differ diff --git a/public/img/custom-provider-screenshot.png b/public/img/custom-provider-screenshot.png deleted file mode 100644 index 314d5d13..00000000 Binary files a/public/img/custom-provider-screenshot.png and /dev/null differ diff --git a/public/img/db-connection-configurate.png b/public/img/db-connection-configurate.png deleted file mode 100644 index 1d2f4d36..00000000 Binary files a/public/img/db-connection-configurate.png and /dev/null differ diff --git a/public/img/db-connection-console-log.png b/public/img/db-connection-console-log.png deleted file mode 100644 index 643b7957..00000000 Binary files a/public/img/db-connection-console-log.png and /dev/null differ diff --git a/public/img/db-connection-create.png b/public/img/db-connection-create.png deleted file mode 100644 index c65030a6..00000000 Binary files a/public/img/db-connection-create.png and /dev/null differ diff --git a/public/img/db-connection-login-script.png b/public/img/db-connection-login-script.png deleted file mode 100644 index 69bf5c89..00000000 Binary files a/public/img/db-connection-login-script.png and /dev/null differ diff --git a/public/img/db-connection-try-fail.png b/public/img/db-connection-try-fail.png deleted file mode 100644 index b62fc9a6..00000000 Binary files a/public/img/db-connection-try-fail.png and /dev/null differ diff --git a/public/img/db-connection-try-ok.png b/public/img/db-connection-try-ok.png deleted file mode 100644 index 2ca689d0..00000000 Binary files a/public/img/db-connection-try-ok.png and /dev/null differ diff --git a/public/img/db-connection-widget.png b/public/img/db-connection-widget.png deleted file mode 100644 index a6a5aa2e..00000000 Binary files a/public/img/db-connection-widget.png and /dev/null differ diff --git a/public/img/enterprise-login.png b/public/img/enterprise-login.png deleted file mode 100644 index 63020574..00000000 Binary files a/public/img/enterprise-login.png and /dev/null differ diff --git a/public/img/environments.png b/public/img/environments.png deleted file mode 100644 index 00b22715..00000000 Binary files a/public/img/environments.png and /dev/null differ diff --git a/public/img/facebook-1.png b/public/img/facebook-1.png deleted file mode 100644 index 336bc68e..00000000 Binary files a/public/img/facebook-1.png and /dev/null differ diff --git a/public/img/facebook-2.png b/public/img/facebook-2.png deleted file mode 100644 index 6aeaaa42..00000000 Binary files a/public/img/facebook-2.png and /dev/null differ diff --git a/public/img/facebook-3.png b/public/img/facebook-3.png deleted file mode 100644 index 0676a6ab..00000000 Binary files a/public/img/facebook-3.png and /dev/null differ diff --git a/public/img/facebook-4.png b/public/img/facebook-4.png deleted file mode 100644 index d510a338..00000000 Binary files a/public/img/facebook-4.png and /dev/null differ diff --git a/public/img/fitbit-register-1.png b/public/img/fitbit-register-1.png deleted file mode 100644 index 9309def2..00000000 Binary files a/public/img/fitbit-register-1.png and /dev/null differ diff --git a/public/img/github-addapp-1.png b/public/img/github-addapp-1.png deleted file mode 100644 index 39e04a7f..00000000 Binary files a/public/img/github-addapp-1.png and /dev/null differ diff --git a/public/img/github-addapp-2.png b/public/img/github-addapp-2.png deleted file mode 100644 index ed110c58..00000000 Binary files a/public/img/github-addapp-2.png and /dev/null differ diff --git a/public/img/github-addapp-3.png b/public/img/github-addapp-3.png deleted file mode 100644 index 1a740f23..00000000 Binary files a/public/img/github-addapp-3.png and /dev/null differ diff --git a/public/img/github-addapp-4.png b/public/img/github-addapp-4.png deleted file mode 100644 index 855f709c..00000000 Binary files a/public/img/github-addapp-4.png and /dev/null differ diff --git a/public/img/goog-apiconsole-1.png b/public/img/goog-apiconsole-1.png deleted file mode 100644 index 487d6ff3..00000000 Binary files a/public/img/goog-apiconsole-1.png and /dev/null differ diff --git a/public/img/goog-apiconsole-2.png b/public/img/goog-apiconsole-2.png deleted file mode 100644 index 44b13272..00000000 Binary files a/public/img/goog-apiconsole-2.png and /dev/null differ diff --git a/public/img/goog-apiconsole-3.png b/public/img/goog-apiconsole-3.png deleted file mode 100644 index 73f03d81..00000000 Binary files a/public/img/goog-apiconsole-3.png and /dev/null differ diff --git a/public/img/goog-apiconsole-4.png b/public/img/goog-apiconsole-4.png deleted file mode 100644 index adb54ca6..00000000 Binary files a/public/img/goog-apiconsole-4.png and /dev/null differ diff --git a/public/img/goog-apiconsole-5.png b/public/img/goog-apiconsole-5.png deleted file mode 100644 index 61ce9ec1..00000000 Binary files a/public/img/goog-apiconsole-5.png and /dev/null differ diff --git a/public/img/goog-oauth2-connection.png b/public/img/goog-oauth2-connection.png deleted file mode 100644 index fc23e3d3..00000000 Binary files a/public/img/goog-oauth2-connection.png and /dev/null differ diff --git a/public/img/iOS-step1.png b/public/img/iOS-step1.png deleted file mode 100644 index d2b5a4ea..00000000 Binary files a/public/img/iOS-step1.png and /dev/null differ diff --git a/public/img/idp-logical.png b/public/img/idp-logical.png deleted file mode 100644 index 5ef6aa23..00000000 Binary files a/public/img/idp-logical.png and /dev/null differ diff --git a/public/img/install-dotnetopenauth-auth0-nuget.png b/public/img/install-dotnetopenauth-auth0-nuget.png deleted file mode 100644 index bcad9c50..00000000 Binary files a/public/img/install-dotnetopenauth-auth0-nuget.png and /dev/null differ diff --git a/public/img/install-servicestack-nuget.png b/public/img/install-servicestack-nuget.png deleted file mode 100644 index 8e943547..00000000 Binary files a/public/img/install-servicestack-nuget.png and /dev/null differ diff --git a/public/img/ios-tutorial.png b/public/img/ios-tutorial.png deleted file mode 100644 index 1826fac3..00000000 Binary files a/public/img/ios-tutorial.png and /dev/null differ diff --git a/public/img/linkedin-devportal-1.png b/public/img/linkedin-devportal-1.png deleted file mode 100644 index 4de0781a..00000000 Binary files a/public/img/linkedin-devportal-1.png and /dev/null differ diff --git a/public/img/linkedin-devportal-2.png b/public/img/linkedin-devportal-2.png deleted file mode 100644 index 258e3f34..00000000 Binary files a/public/img/linkedin-devportal-2.png and /dev/null differ diff --git a/public/img/linkedin-devportal-3.png b/public/img/linkedin-devportal-3.png deleted file mode 100644 index 8cff9c08..00000000 Binary files a/public/img/linkedin-devportal-3.png and /dev/null differ diff --git a/public/img/linkedin-devportal-4.png b/public/img/linkedin-devportal-4.png deleted file mode 100644 index fd04c936..00000000 Binary files a/public/img/linkedin-devportal-4.png and /dev/null differ diff --git a/public/img/linkedin-devportal-5.png b/public/img/linkedin-devportal-5.png deleted file mode 100644 index 3e15f57b..00000000 Binary files a/public/img/linkedin-devportal-5.png and /dev/null differ diff --git a/public/img/linkedin-devportal-6.png b/public/img/linkedin-devportal-6.png deleted file mode 100644 index 38df87ba..00000000 Binary files a/public/img/linkedin-devportal-6.png and /dev/null differ diff --git a/public/img/login-widget-servicestack.png b/public/img/login-widget-servicestack.png deleted file mode 100644 index 1471fbfc..00000000 Binary files a/public/img/login-widget-servicestack.png and /dev/null differ diff --git a/public/img/logo-fiddle.png b/public/img/logo-fiddle.png deleted file mode 100644 index 58af40ed..00000000 Binary files a/public/img/logo-fiddle.png and /dev/null differ diff --git a/public/img/ma-portal-1.png b/public/img/ma-portal-1.png deleted file mode 100644 index 1e852da3..00000000 Binary files a/public/img/ma-portal-1.png and /dev/null differ diff --git a/public/img/ma-portal-2.png b/public/img/ma-portal-2.png deleted file mode 100644 index 7a5ff72f..00000000 Binary files a/public/img/ma-portal-2.png and /dev/null differ diff --git a/public/img/node-linux.png b/public/img/node-linux.png deleted file mode 100755 index cbbc983c..00000000 Binary files a/public/img/node-linux.png and /dev/null differ diff --git a/public/img/node-mac.png b/public/img/node-mac.png deleted file mode 100755 index b8d90ffc..00000000 Binary files a/public/img/node-mac.png and /dev/null differ diff --git a/public/img/node-windows.png b/public/img/node-windows.png deleted file mode 100755 index ef893c32..00000000 Binary files a/public/img/node-windows.png and /dev/null differ diff --git a/public/img/o365-portal-1.png b/public/img/o365-portal-1.png deleted file mode 100644 index ed2b05fe..00000000 Binary files a/public/img/o365-portal-1.png and /dev/null differ diff --git a/public/img/o365-portal-2.png b/public/img/o365-portal-2.png deleted file mode 100644 index e885ef52..00000000 Binary files a/public/img/o365-portal-2.png and /dev/null differ diff --git a/public/img/o365-portal-3.png b/public/img/o365-portal-3.png deleted file mode 100644 index 36b4714e..00000000 Binary files a/public/img/o365-portal-3.png and /dev/null differ diff --git a/public/img/o365-portal-4.png b/public/img/o365-portal-4.png deleted file mode 100644 index 9524d34b..00000000 Binary files a/public/img/o365-portal-4.png and /dev/null differ diff --git a/public/img/package.png b/public/img/package.png deleted file mode 100755 index 46c69434..00000000 Binary files a/public/img/package.png and /dev/null differ diff --git a/public/img/paypal-devportal-1.png b/public/img/paypal-devportal-1.png deleted file mode 100644 index 5af0a54b..00000000 Binary files a/public/img/paypal-devportal-1.png and /dev/null differ diff --git a/public/img/paypal-devportal-2.png b/public/img/paypal-devportal-2.png deleted file mode 100644 index bad4d6db..00000000 Binary files a/public/img/paypal-devportal-2.png and /dev/null differ diff --git a/public/img/paypal-devportal-3.png b/public/img/paypal-devportal-3.png deleted file mode 100644 index 238d0c01..00000000 Binary files a/public/img/paypal-devportal-3.png and /dev/null differ diff --git a/public/img/paypal-devportal-4.png b/public/img/paypal-devportal-4.png deleted file mode 100644 index 9e08463f..00000000 Binary files a/public/img/paypal-devportal-4.png and /dev/null differ diff --git a/public/img/ping-1.png b/public/img/ping-1.png deleted file mode 100644 index e5b0ad4e..00000000 Binary files a/public/img/ping-1.png and /dev/null differ diff --git a/public/img/ping-10.png b/public/img/ping-10.png deleted file mode 100644 index c0173c9e..00000000 Binary files a/public/img/ping-10.png and /dev/null differ diff --git a/public/img/ping-11.png b/public/img/ping-11.png deleted file mode 100644 index 6b63c2e1..00000000 Binary files a/public/img/ping-11.png and /dev/null differ diff --git a/public/img/ping-12.png b/public/img/ping-12.png deleted file mode 100644 index b0e39935..00000000 Binary files a/public/img/ping-12.png and /dev/null differ diff --git a/public/img/ping-13.png b/public/img/ping-13.png deleted file mode 100644 index 6d7f36ce..00000000 Binary files a/public/img/ping-13.png and /dev/null differ diff --git a/public/img/ping-14.png b/public/img/ping-14.png deleted file mode 100644 index 113904eb..00000000 Binary files a/public/img/ping-14.png and /dev/null differ diff --git a/public/img/ping-15.png b/public/img/ping-15.png deleted file mode 100644 index 79fe1d2c..00000000 Binary files a/public/img/ping-15.png and /dev/null differ diff --git a/public/img/ping-16.png b/public/img/ping-16.png deleted file mode 100644 index 86f2ecd2..00000000 Binary files a/public/img/ping-16.png and /dev/null differ diff --git a/public/img/ping-17.png b/public/img/ping-17.png deleted file mode 100644 index 7b14f1b9..00000000 Binary files a/public/img/ping-17.png and /dev/null differ diff --git a/public/img/ping-18.png b/public/img/ping-18.png deleted file mode 100644 index 7d360c69..00000000 Binary files a/public/img/ping-18.png and /dev/null differ diff --git a/public/img/ping-19.png b/public/img/ping-19.png deleted file mode 100644 index 76a08770..00000000 Binary files a/public/img/ping-19.png and /dev/null differ diff --git a/public/img/ping-2.png b/public/img/ping-2.png deleted file mode 100644 index 82a5d0cf..00000000 Binary files a/public/img/ping-2.png and /dev/null differ diff --git a/public/img/ping-3.png b/public/img/ping-3.png deleted file mode 100644 index 23f07f7b..00000000 Binary files a/public/img/ping-3.png and /dev/null differ diff --git a/public/img/ping-4.png b/public/img/ping-4.png deleted file mode 100644 index 4135205c..00000000 Binary files a/public/img/ping-4.png and /dev/null differ diff --git a/public/img/ping-5.png b/public/img/ping-5.png deleted file mode 100644 index 7a5c1966..00000000 Binary files a/public/img/ping-5.png and /dev/null differ diff --git a/public/img/ping-6.png b/public/img/ping-6.png deleted file mode 100644 index 0ddbc049..00000000 Binary files a/public/img/ping-6.png and /dev/null differ diff --git a/public/img/ping-7.png b/public/img/ping-7.png deleted file mode 100644 index bb771e91..00000000 Binary files a/public/img/ping-7.png and /dev/null differ diff --git a/public/img/ping-8.png b/public/img/ping-8.png deleted file mode 100644 index a444dd6d..00000000 Binary files a/public/img/ping-8.png and /dev/null differ diff --git a/public/img/ping-9.png b/public/img/ping-9.png deleted file mode 100644 index d71a90b9..00000000 Binary files a/public/img/ping-9.png and /dev/null differ diff --git a/public/img/protocols-oauth-code.png b/public/img/protocols-oauth-code.png deleted file mode 100755 index b6efec6e..00000000 Binary files a/public/img/protocols-oauth-code.png and /dev/null differ diff --git a/public/img/protocols-oauth-implicit.png b/public/img/protocols-oauth-implicit.png deleted file mode 100755 index 7194f66e..00000000 Binary files a/public/img/protocols-oauth-implicit.png and /dev/null differ diff --git a/public/img/rules-pipeline.png b/public/img/rules-pipeline.png deleted file mode 100644 index 678e6373..00000000 Binary files a/public/img/rules-pipeline.png and /dev/null differ diff --git a/public/img/rules.png b/public/img/rules.png deleted file mode 100644 index dd8497cb..00000000 Binary files a/public/img/rules.png and /dev/null differ diff --git a/public/img/salesforce-register-1.png b/public/img/salesforce-register-1.png deleted file mode 100644 index bf435c96..00000000 Binary files a/public/img/salesforce-register-1.png and /dev/null differ diff --git a/public/img/salesforce-register-2.png b/public/img/salesforce-register-2.png deleted file mode 100644 index b4fb3458..00000000 Binary files a/public/img/salesforce-register-2.png and /dev/null differ diff --git a/public/img/salesforce-register-3.png b/public/img/salesforce-register-3.png deleted file mode 100644 index 3fe5b2fa..00000000 Binary files a/public/img/salesforce-register-3.png and /dev/null differ diff --git a/public/img/settings-callback-aspnet.png b/public/img/settings-callback-aspnet.png deleted file mode 100644 index bafb9148..00000000 Binary files a/public/img/settings-callback-aspnet.png and /dev/null differ diff --git a/public/img/settings-callback-rails.png b/public/img/settings-callback-rails.png deleted file mode 100644 index c8ab781c..00000000 Binary files a/public/img/settings-callback-rails.png and /dev/null differ diff --git a/public/img/settings-callback.png b/public/img/settings-callback.png deleted file mode 100644 index 8c5778e8..00000000 Binary files a/public/img/settings-callback.png and /dev/null differ diff --git a/public/img/signin.png b/public/img/signin.png deleted file mode 100644 index 5c76c572..00000000 Binary files a/public/img/signin.png and /dev/null differ diff --git a/public/img/siteminder-attributes.png b/public/img/siteminder-attributes.png deleted file mode 100644 index cdf18d7e..00000000 Binary files a/public/img/siteminder-attributes.png and /dev/null differ diff --git a/public/img/siteminder-encryption.png b/public/img/siteminder-encryption.png deleted file mode 100644 index 941013f9..00000000 Binary files a/public/img/siteminder-encryption.png and /dev/null differ diff --git a/public/img/siteminder-general.png b/public/img/siteminder-general.png deleted file mode 100644 index 9969e1fd..00000000 Binary files a/public/img/siteminder-general.png and /dev/null differ diff --git a/public/img/siteminder-nameids.png b/public/img/siteminder-nameids.png deleted file mode 100644 index 6efc7d34..00000000 Binary files a/public/img/siteminder-nameids.png and /dev/null differ diff --git a/public/img/siteminder-slo.png b/public/img/siteminder-slo.png deleted file mode 100644 index 9ea6000c..00000000 Binary files a/public/img/siteminder-slo.png and /dev/null differ diff --git a/public/img/siteminder-sso.png b/public/img/siteminder-sso.png deleted file mode 100644 index c86bdd3b..00000000 Binary files a/public/img/siteminder-sso.png and /dev/null differ diff --git a/public/img/siteminder-users.png b/public/img/siteminder-users.png deleted file mode 100644 index 691fe88a..00000000 Binary files a/public/img/siteminder-users.png and /dev/null differ diff --git a/public/img/twitter-api-1.png b/public/img/twitter-api-1.png deleted file mode 100644 index 45e98b57..00000000 Binary files a/public/img/twitter-api-1.png and /dev/null differ diff --git a/public/img/twitter-api-2.png b/public/img/twitter-api-2.png deleted file mode 100644 index 826aca2b..00000000 Binary files a/public/img/twitter-api-2.png and /dev/null differ diff --git a/public/img/twitter-api-3.png b/public/img/twitter-api-3.png deleted file mode 100644 index ccff69bf..00000000 Binary files a/public/img/twitter-api-3.png and /dev/null differ diff --git a/public/img/twitter-api-4.png b/public/img/twitter-api-4.png deleted file mode 100644 index e459a50e..00000000 Binary files a/public/img/twitter-api-4.png and /dev/null differ diff --git a/public/img/twitter-api-5.png b/public/img/twitter-api-5.png deleted file mode 100644 index 456862fc..00000000 Binary files a/public/img/twitter-api-5.png and /dev/null differ diff --git a/public/img/vkontakte-add-connection.png b/public/img/vkontakte-add-connection.png deleted file mode 100644 index a024ca9c..00000000 Binary files a/public/img/vkontakte-add-connection.png and /dev/null differ diff --git a/public/img/vkontakte-create-app.png b/public/img/vkontakte-create-app.png deleted file mode 100644 index 314de701..00000000 Binary files a/public/img/vkontakte-create-app.png and /dev/null differ diff --git a/public/img/vkontakte-register-app.png b/public/img/vkontakte-register-app.png deleted file mode 100644 index d7e7bdc8..00000000 Binary files a/public/img/vkontakte-register-app.png and /dev/null differ diff --git a/public/img/vkontakte-validate-create-app.png b/public/img/vkontakte-validate-create-app.png deleted file mode 100644 index 52d5e70c..00000000 Binary files a/public/img/vkontakte-validate-create-app.png and /dev/null differ diff --git a/public/img/waad-0.png b/public/img/waad-0.png deleted file mode 100644 index d8a279a7..00000000 Binary files a/public/img/waad-0.png and /dev/null differ diff --git a/public/img/waad-1.png b/public/img/waad-1.png deleted file mode 100644 index 580efbbe..00000000 Binary files a/public/img/waad-1.png and /dev/null differ diff --git a/public/img/waad-10.png b/public/img/waad-10.png deleted file mode 100644 index 21cd47ae..00000000 Binary files a/public/img/waad-10.png and /dev/null differ diff --git a/public/img/waad-2.png b/public/img/waad-2.png deleted file mode 100644 index 02134abe..00000000 Binary files a/public/img/waad-2.png and /dev/null differ diff --git a/public/img/waad-3.png b/public/img/waad-3.png deleted file mode 100644 index 0808e49e..00000000 Binary files a/public/img/waad-3.png and /dev/null differ diff --git a/public/img/waad-4.png b/public/img/waad-4.png deleted file mode 100644 index 56549620..00000000 Binary files a/public/img/waad-4.png and /dev/null differ diff --git a/public/img/waad-5.png b/public/img/waad-5.png deleted file mode 100644 index 703158bf..00000000 Binary files a/public/img/waad-5.png and /dev/null differ diff --git a/public/img/waad-6.png b/public/img/waad-6.png deleted file mode 100644 index 9211fcdb..00000000 Binary files a/public/img/waad-6.png and /dev/null differ diff --git a/public/img/waad-7.png b/public/img/waad-7.png deleted file mode 100644 index 7f70a4d2..00000000 Binary files a/public/img/waad-7.png and /dev/null differ diff --git a/public/img/waad-8.png b/public/img/waad-8.png deleted file mode 100644 index d9210062..00000000 Binary files a/public/img/waad-8.png and /dev/null differ diff --git a/public/img/waad-9.png b/public/img/waad-9.png deleted file mode 100644 index bb908568..00000000 Binary files a/public/img/waad-9.png and /dev/null differ diff --git a/public/img/widget-1.acorn b/public/img/widget-1.acorn deleted file mode 100644 index f31f4864..00000000 Binary files a/public/img/widget-1.acorn and /dev/null differ diff --git a/public/img/widget-1.png b/public/img/widget-1.png deleted file mode 100644 index b4e3230e..00000000 Binary files a/public/img/widget-1.png and /dev/null differ diff --git a/public/img/widget-2.acorn b/public/img/widget-2.acorn deleted file mode 100644 index 5fb5fb9d..00000000 Binary files a/public/img/widget-2.acorn and /dev/null differ diff --git a/public/img/widget-2.png b/public/img/widget-2.png deleted file mode 100644 index 36d7e58d..00000000 Binary files a/public/img/widget-2.png and /dev/null differ diff --git a/public/img/widget-3.acorn b/public/img/widget-3.acorn deleted file mode 100644 index af88766f..00000000 Binary files a/public/img/widget-3.acorn and /dev/null differ diff --git a/public/img/widget-3.png b/public/img/widget-3.png deleted file mode 100644 index df9390d8..00000000 Binary files a/public/img/widget-3.png and /dev/null differ diff --git a/public/img/widget-4.acorn b/public/img/widget-4.acorn deleted file mode 100644 index 62718bb2..00000000 Binary files a/public/img/widget-4.acorn and /dev/null differ diff --git a/public/img/widget-4.png b/public/img/widget-4.png deleted file mode 100644 index 655379bc..00000000 Binary files a/public/img/widget-4.png and /dev/null differ diff --git a/public/img/widget-custom.acorn b/public/img/widget-custom.acorn deleted file mode 100644 index 42c36d66..00000000 Binary files a/public/img/widget-custom.acorn and /dev/null differ diff --git a/public/img/widget-custom.png b/public/img/widget-custom.png deleted file mode 100644 index b2367c1a..00000000 Binary files a/public/img/widget-custom.png and /dev/null differ diff --git a/public/img/widget-customized-reset.png b/public/img/widget-customized-reset.png deleted file mode 100755 index 73669932..00000000 Binary files a/public/img/widget-customized-reset.png and /dev/null differ diff --git a/public/img/widget-customized-signup.png b/public/img/widget-customized-signup.png deleted file mode 100755 index 1718f876..00000000 Binary files a/public/img/widget-customized-signup.png and /dev/null differ diff --git a/public/img/widget-customized.acorn b/public/img/widget-customized.acorn deleted file mode 100644 index f81e30f6..00000000 Binary files a/public/img/widget-customized.acorn and /dev/null differ diff --git a/public/img/widget-customized.png b/public/img/widget-customized.png deleted file mode 100644 index 9b172872..00000000 Binary files a/public/img/widget-customized.png and /dev/null differ diff --git a/public/img/widget-error.png b/public/img/widget-error.png deleted file mode 100644 index 015293df..00000000 Binary files a/public/img/widget-error.png and /dev/null differ diff --git a/public/img/widget-in-aspnet.png b/public/img/widget-in-aspnet.png deleted file mode 100644 index b4b97b24..00000000 Binary files a/public/img/widget-in-aspnet.png and /dev/null differ diff --git a/public/img/widget-large-full.acorn b/public/img/widget-large-full.acorn deleted file mode 100644 index 5a7d73f2..00000000 Binary files a/public/img/widget-large-full.acorn and /dev/null differ diff --git a/public/img/widget-large-full.png b/public/img/widget-large-full.png deleted file mode 100644 index 38611f6f..00000000 Binary files a/public/img/widget-large-full.png and /dev/null differ diff --git a/public/img/widget-large.acorn b/public/img/widget-large.acorn deleted file mode 100644 index fa711c50..00000000 Binary files a/public/img/widget-large.acorn and /dev/null differ diff --git a/public/img/widget-large.png b/public/img/widget-large.png deleted file mode 100644 index 016d1fca..00000000 Binary files a/public/img/widget-large.png and /dev/null differ diff --git a/public/img/widget-numbered.acorn b/public/img/widget-numbered.acorn deleted file mode 100644 index 69de7000..00000000 Binary files a/public/img/widget-numbered.acorn and /dev/null differ diff --git a/public/img/widget-numbered.png b/public/img/widget-numbered.png deleted file mode 100644 index 36e14afb..00000000 Binary files a/public/img/widget-numbered.png and /dev/null differ diff --git a/public/img/widget-onestep-numbered.png b/public/img/widget-onestep-numbered.png deleted file mode 100644 index 6928db06..00000000 Binary files a/public/img/widget-onestep-numbered.png and /dev/null differ diff --git a/public/img/widget-onestep.acorn b/public/img/widget-onestep.acorn deleted file mode 100644 index d220d7f2..00000000 Binary files a/public/img/widget-onestep.acorn and /dev/null differ diff --git a/public/img/widget-onestep.png b/public/img/widget-onestep.png deleted file mode 100644 index a91ed4bc..00000000 Binary files a/public/img/widget-onestep.png and /dev/null differ diff --git a/public/img/widget-prov-in-aspnet.png b/public/img/widget-prov-in-aspnet.png deleted file mode 100644 index 03c1a467..00000000 Binary files a/public/img/widget-prov-in-aspnet.png and /dev/null differ diff --git a/public/img/win8-cs-step1.png b/public/img/win8-cs-step1.png deleted file mode 100644 index 6d07df90..00000000 Binary files a/public/img/win8-cs-step1.png and /dev/null differ diff --git a/public/img/win8-step1.png b/public/img/win8-step1.png deleted file mode 100755 index 470bbc08..00000000 Binary files a/public/img/win8-step1.png and /dev/null differ diff --git a/public/img/win8-step2.png b/public/img/win8-step2.png deleted file mode 100755 index d1f61dda..00000000 Binary files a/public/img/win8-step2.png and /dev/null differ diff --git a/public/img/win8-step3.png b/public/img/win8-step3.png deleted file mode 100755 index 9b9fd21a..00000000 Binary files a/public/img/win8-step3.png and /dev/null differ diff --git a/public/img/win8-step4.png b/public/img/win8-step4.png deleted file mode 100755 index 4eef0d39..00000000 Binary files a/public/img/win8-step4.png and /dev/null differ diff --git a/public/img/windows-phone-tutorial.png b/public/img/windows-phone-tutorial.png deleted file mode 100644 index c1fc73db..00000000 Binary files a/public/img/windows-phone-tutorial.png and /dev/null differ diff --git a/public/img/windowsstore-javascript-step1.png b/public/img/windowsstore-javascript-step1.png deleted file mode 100644 index e66be4c5..00000000 Binary files a/public/img/windowsstore-javascript-step1.png and /dev/null differ diff --git a/public/img/windowsstore-javascript-step2.png b/public/img/windowsstore-javascript-step2.png deleted file mode 100644 index ffff6c46..00000000 Binary files a/public/img/windowsstore-javascript-step2.png and /dev/null differ diff --git a/public/img/windowsstore-javascript-step3.png b/public/img/windowsstore-javascript-step3.png deleted file mode 100644 index 8f0e8910..00000000 Binary files a/public/img/windowsstore-javascript-step3.png and /dev/null differ diff --git a/public/img/windowsstore-javascript-step5.png b/public/img/windowsstore-javascript-step5.png deleted file mode 100644 index 2b5007e9..00000000 Binary files a/public/img/windowsstore-javascript-step5.png and /dev/null differ diff --git a/public/img/windowsstore-javascript-step6.2.png b/public/img/windowsstore-javascript-step6.2.png deleted file mode 100644 index 8683fb52..00000000 Binary files a/public/img/windowsstore-javascript-step6.2.png and /dev/null differ diff --git a/public/img/windowsstore-javascript-step7.png b/public/img/windowsstore-javascript-step7.png deleted file mode 100644 index 10d3f9a2..00000000 Binary files a/public/img/windowsstore-javascript-step7.png and /dev/null differ diff --git a/public/img/windowsstore-step7.1.png b/public/img/windowsstore-step7.1.png deleted file mode 100644 index e593b621..00000000 Binary files a/public/img/windowsstore-step7.1.png and /dev/null differ diff --git a/public/img/windowsstore-step7.png b/public/img/windowsstore-step7.png deleted file mode 100644 index aa20c15f..00000000 Binary files a/public/img/windowsstore-step7.png and /dev/null differ diff --git a/public/img/wp8-step1.png b/public/img/wp8-step1.png deleted file mode 100755 index 5d505094..00000000 Binary files a/public/img/wp8-step1.png and /dev/null differ diff --git a/public/img/wp8-step2.png b/public/img/wp8-step2.png deleted file mode 100755 index 42b8486f..00000000 Binary files a/public/img/wp8-step2.png and /dev/null differ diff --git a/public/img/wp8-step3.png b/public/img/wp8-step3.png deleted file mode 100755 index 626ebba8..00000000 Binary files a/public/img/wp8-step3.png and /dev/null differ diff --git a/public/img/wp8-step4.png b/public/img/wp8-step4.png deleted file mode 100755 index 1e04e2b9..00000000 Binary files a/public/img/wp8-step4.png and /dev/null differ diff --git a/public/img/wp8-step5.png b/public/img/wp8-step5.png deleted file mode 100755 index d8fbd603..00000000 Binary files a/public/img/wp8-step5.png and /dev/null differ diff --git a/public/img/wpf-winforms-step1.png b/public/img/wpf-winforms-step1.png deleted file mode 100644 index b6eb5356..00000000 Binary files a/public/img/wpf-winforms-step1.png and /dev/null differ diff --git a/public/img/xamarin.auth0client.png b/public/img/xamarin.auth0client.png deleted file mode 100644 index 4af1f652..00000000 Binary files a/public/img/xamarin.auth0client.png and /dev/null differ diff --git a/public/img/yandex-add-connection.png b/public/img/yandex-add-connection.png deleted file mode 100644 index 3d22c29d..00000000 Binary files a/public/img/yandex-add-connection.png and /dev/null differ diff --git a/public/img/yandex-create-app.png b/public/img/yandex-create-app.png deleted file mode 100644 index 85976877..00000000 Binary files a/public/img/yandex-create-app.png and /dev/null differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 00000000..c7b00363 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Sitemap: https://auth0.com/docs/sitemap.xml diff --git a/sdk/widget_script_url.js b/sdk/widget_script_url.js deleted file mode 100644 index 99f8968f..00000000 --- a/sdk/widget_script_url.js +++ /dev/null @@ -1,32 +0,0 @@ -var nconf = require('nconf'); -var getDb = require('../lib/data'); - -var env = nconf.get('NODE_ENV'); -var cdn = nconf.get('CDN'); -var disable_cdn = nconf.get('DISABLE_CDN'); -var DOMAIN_URL_SDK = nconf.get('DOMAIN_URL_SDK'); -var DOMAIN_URL_SERVER = nconf.get('DOMAIN_URL_SERVER'); - -exports.get = function (clientID, done) { - getDb(function (db) { - db.collection('clients').findOne({clientID: clientID }, function (err, client) { - if (err) return done(err); - var tenant_domain = 'https://' + DOMAIN_URL_SERVER.replace('{tenant}', client ? client.tenant : 'YOUR-DOMAIN'); - - var sdk_url; - - if (env === 'production' && !disable_cdn) { - if (cdn) { - sdk_url = 'https://' + DOMAIN_URL_SDK + '/auth0.js#client=' + clientID + '&cdn=https://' + cdn; - } else { - sdk_url = 'https://' + DOMAIN_URL_SDK + '/auth0.js#client=' + clientID; - } - } else { - sdk_url = 'https://' + DOMAIN_URL_SDK + '/auth0.js#client=' + clientID + '&cdn=' + tenant_domain + '&assets=' + tenant_domain; - } - - done(null, sdk_url, tenant_domain, client); - - }); - }); -}; \ No newline at end of file diff --git a/settings.json b/settings.json index 360979e0..ba268191 100644 --- a/settings.json +++ b/settings.json @@ -5,47 +5,155 @@ "navigation": [ { "title": "Getting Started", + "icon": "icon-star", "items": [ - { "title": "Start here", "url": "/" } + { "title": "Quickstarts", "url": "/" } ] }, { - "title": "Tutorials Web", + "title": "Web Apps", + "icon": "icon-laptop", + "disabled": true, "items": [ - { "title": "Node", "url": "/nodejs-tutorial" }, + { "title": "Node.js", "url": "/server-platforms/nodejs" }, { "title": "ASP.NET", "url": "/aspnet-tutorial" }, - { "title": "Web API & SPA", "url": "/webapi" }, - { "title": "Windows Azure", "url": "/azure-tutorial" }, + { "title": "ASP.NET (OWIN)", "url": "/aspnet-owin-tutorial" }, { "title": "ServiceStack", "url": "/servicestack-tutorial" }, - { "title": "WCF", "url": "/wcf-tutorial" }, - { "title": "Ruby on Rails", "url": "/rails-tutorial" } + { "title": "PHP", "url": "/server-platforms/php" }, + { "title": "PHP (Laravel)", "url": "/laravel-tutorial" }, + { "title": "Ruby", "url": "/server-platforms/rails" }, + { "title": "Java", "url": "/server-platforms/java" }, + { "title": "Python", "url": "/server-platforms/python" } ] }, { - "title": "Tutorials (Mobile/Native)", + "title": "Web APIs", + "icon": "icon-terminal", + "disabled": true, + "items": [ + { "title": "ASP.NET Web API", "url": "/server-apis/aspnet-webapi" }, + { "title": "ASP.NET Web API (OWIN)", "url": "/server-apis/webapi-owin" }, + { "title": "Node.js API", "url": "/server-apis/nodejs" }, + { "title": "Ruby API", "url": "/server-apis/ruby" }, + { "title": "Ruby On Rails API", "url": "/server-apis/rails" }, + { "title": "PHP API", "url": "/server-apis/php" }, + { "title": "PHP (Laravel) API", "url": "/server-apis/php-laravel" }, + { "title": "Java API", "url": "/server-apis/java" }, + { "title": "Python API", "url": "/server-apis/python" }, + { "title": ".NET WCF", "url": "/wcf-tutorial" }, + { "title": "AWS", "url": "/server-apis/aws" }, + { "title": "Azure Mobile Services", "url": "/server-apis/azure-mobile-services" }, + { "title": "Firebase", "url": "/server-apis/firebase" }, + { "title": "Salesforce", "url": "/server-apis/salesforce" }, + { "title": "Salesforce (Sandbox)", "url": "/server-apis/salesforce-sandbox" }, + { "title": "SAP OData", "url": "/server-apis/sap-odata" } + ] + }, + { + "title": "Mobile/Native", + "icon": "icon-mobile-phone", + "disabled": true, "items": [ { "title": "iOS", "url": "/ios-tutorial" }, { "title": "Android", "url": "/android-tutorial" }, { "title": "Windows Phone", "url": "/windowsphone-tutorial" }, { "title": "Xamarin", "url": "/xamarin-tutorial" }, + { "title": "PhoneGap", "url": "/native-platforms/cordova" }, + { "title": "Cordova", "url": "/native-platforms/cordova" }, + { "title": "Ionic", "url": "/native-platforms/ionic" }, { "title": "Windows 8 (JS)", "url": "/win8-tutorial" }, { "title": "Windows 8 (C#)", "url": "/win8-cs-tutorial" }, { "title": "WPF / Winforms", "url": "/wpf-winforms-tutorial" } ] }, { - "title": "Advanced topics", + "title": "HTML5", + "icon": "icon-html5", + "disabled": true, + "items": [ + { "title": "Single Page Apps", "url": "/client-platforms/vanillajs" }, + { "title": "Vanilla JS", "url": "/client-platforms/vanillajs" }, + { "title": "Angular.js", "url": "/client-platforms/angularjs" }, + { "title": "jQuery", "url": "/client-platforms/jquery" } + ] + }, + { + "title": "Libraries", + "icon": "icon-book", + "items": [ + { "title": "Auth0.js", "url": "/auth0js" }, + { "title": "Lock", "url": "/lock" }, + { "title": "Browser", "url": "/quickstart/spa"}, + { "title": "Mobile & Native", "url": "/quickstart/native-mobile" }, + { "title": "Server", "url": "/quickstart/webapp" } + ] + }, + { + "title": "API Reference", + "icon": "icon-cogs", + "items": [ + { "title": "API", "url": "/api"}, + { "title": "API v2 (beta)", "url": "/apiv2"}, + { "title": "Authentication Endpoints", "url": "/auth-api"} + ] + }, + { + "title": "Integrations", + "icon": "icon-cogs", + "disabled" : true, + "items": [ + { "title": "AD RMS", "url": "/integrations/ad-rms"}, + { "title": "Box", "url": "/integrations/box"}, + { "title": "CloudBees", "url": "/integrations/cloudbees"}, + { "title": "Concur (Beta)", "url": "/integrations/concur"}, + { "title": "Dropbox", "url": "/integrations/dropbox"}, + { "title": "Dynamic CRM", "url": "/integrations/dynamic-crm"}, + { "title": "Echosign", "url": "/integrations/echosign"}, + { "title": "Egnyte", "url": "/integrations/egnyte"}, + { "title": "New Relic", "url": "/integrations/new-relic" }, + { "title": "Office 365", "url": "/integrations/office-365" }, + { "title": "Salesforce", "url": "/integrations/salesforce" }, + { "title": "Sharepoint", "url": "/integrations/sharepoint" }, + { "title": "SpringCM", "url": "/integrations/springcm" }, + { "title": "ZenDesk", "url": "/integrations/zendesk" }, + { "title": "Zoom", "url": "/integrations/zoom" } + ] + }, + { + "title": "Core Concepts", + "icon": "icon-question-sign", + "items": [ + { "title": "Applications", "url":"/applications"}, + { "title": "Rules", "url":"/rules"} + ] + }, + { + "title": "Help Topics", + "icon": "icon-question-sign", "items": [ - { "title": "Normalized Profile", "url":"/user-profile"}, - { "title": "Login Widget", "url":"/login-widget"}, + { "title": "Real life scenarios", "url":"/scenarios"}, + { "title": "User Profile", "url":"/user-profile"}, + { "title": "Web Apps vs Web APIs", "url": "/apps-apis" }, { "title": "Logout", "url":"/logout"}, - { "title": "Rules", "url":"/rules"}, + { "title": "Connector", "url":"/connector"}, + { "title": "Single Sign On (SSO)", "url":"/sso/single-sign-on"}, + { "title": "After Login", "url":"/what-to-do-once-the-user-is-logged-in/index"}, + { "title": "Rules Repository", "url": "https://github.com/auth0/rules" }, + { "title": "Active Directory", "url": "/ad" }, { "title": "Database Connections", "url": "/mysql-connection-tutorial" }, - { "title": "API Reference", "url":"/api-reference"}, + { "title": "Passwordless Connections", "url": "/passwordless" }, { "title": "Protocols", "url":"/protocols"}, + { "title": "Sequence Diagrams", "url":"/sequence-diagrams"}, + { "title": "Refresh token", "url":"/refresh-token"}, { "title": "Identity Providers", "url": "/identityproviders" }, { "title": "SAML", "url":"/saml-configuration"}, - { "title": "Link Accounts", "url": "/link-accounts" } + { "title": "Link Accounts", "url": "/link-accounts" }, + { "title": "Custom Signup", "url": "/custom-signup" }, + { "title": "Multifactor Auth (beta)", "url": "/mfa" }, + { "title": "SharePoint Apps (beta)", "url": "/sharepoint-apps"}, + { "title": "Password Policies (beta)", "url": "/password-strength"}, + { "title": "User Migration (beta)", "url": "/migrating"}, + { "title": "Legacy Widget", "url":"/login-widget2"} ] } ], diff --git a/test/index.test.js b/test/index.test.js new file mode 100644 index 00000000..ed0bbad2 --- /dev/null +++ b/test/index.test.js @@ -0,0 +1,27 @@ +/** + * Module dependencies. + */ + +var docsapp = require('../app'); +var nconf = require('nconf'); +var request = require('request'); +var assert = require('assert'); + +describe('Application', function() { + after(function (done) { + docsapp.stop(done); + }); + + describe('GET /test', function(){ + it('should respond OK with json', function(done){ + + console.log(nconf.get('BASE_URL')); + + request.get('http://localhost:' + nconf.get('PORT') + '/test', function (err, resp, body) { + assert.equal(resp.statusCode, 200); + done(); + }); + }); + }); + +}); diff --git a/themes/default/index.js b/themes/default/index.js index 20b712b1..ea28d4f5 100644 --- a/themes/default/index.js +++ b/themes/default/index.js @@ -1,3 +1,4 @@ +var nconf = require('nconf'); var Theme = function(docsapp) { this._docsapp = docsapp; @@ -6,11 +7,46 @@ var Theme = function(docsapp) { Theme.prototype._preRender = function(request, response, next) { var settings = this._docsapp.getSettings(); + var sections = response.doc.getSections(); + var title = response.doc.getMeta()['title'] || alternative_title(sections.content); + var conanicalUrl = response.doc.getMeta()['canonical']; + if (conanicalUrl) { + conanicalUrl = nconf.get('DOMAIN_URL_DOCS') + nconf.get('BASE_URL') + conanicalUrl; + } + response.locals.site = response.locals.site || {}; response.locals.site.title = settings['title'] || 'Default'; response.locals.site.menus = settings['menus'] || {}; - response.locals.title = response.doc.getMeta()['title'] || 'Document'; + response.locals.title = title; + response.locals.canonicalUrl = conanicalUrl; + response.locals.env = { + BASE_URL: nconf.get('BASE_URL'), + AUTH0_DOMAIN: nconf.get('AUTH0_DOMAIN'), + AUTH0_CLIENT_ID: nconf.get('AUTH0_CLIENT_ID'), + COOKIE_SCOPE: nconf.get('COOKIE_SCOPE'), + DOMAIN_URL_SERVER: nconf.get('DOMAIN_URL_SERVER'), + SEGMENT_KEY: nconf.get('SEGMENT_KEY') + }; + next(); }; -module.exports = Theme; \ No newline at end of file +module.exports = Theme; + +/** + * Matches an alternative + * title from the content + * + * @param {String} content + * @return {String} title + * @api private + */ + +function alternative_title (content) { + var regex = /\#{1}[^\n\#]+/g; + var match = content.match(regex); + + if (match && match.length) match = match[0].slice(1).trim(); + + return match || 'Document'; +} diff --git a/themes/default/public/css/default.css b/themes/default/public/css/default.css index 0f82172e..f12e3142 100644 --- a/themes/default/public/css/default.css +++ b/themes/default/public/css/default.css @@ -1,243 +1,3 @@ -html, body { - font-family: 'Open Sans', Helvetica,Arial,sans-serif; - font-size: 13px; -} - -html, body { - background-color: #F1F1F1; -} - -html { - overflow-y: scroll; -} - -body { - position: relative; -} - -h1, h2, h3, h4, h5, h6 { - color: #394755; - font-weight: bold; - margin: 28px 0 17px 0; -} - -h1 { - font-size: 28px; - } - -h2 { - margin-bottom: 7px; - font-size: 18px; - color: #222; -} - -h3 { - font-size: 15px; - margin-bottom: 3px; - margin-top: 0px; - } - -h4 { - font-size: 13px; - margin-bottom: 3px; - margin-top: 0px; -} - -blockquote { - border-left: 0px; - padding: 0px; - margin: 0 0 20px; -} - -blockquote p { - font-size: 13px; - text-shadow: 0 1px 0 white; - padding: 10px; - background: #F9FAE9; - border: 1px solid #D7D9C9; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - -ms-border-radius: 5px; - -o-border-radius: 5px; - border-radius: 5px; - margin-right: 0 !important; - margin-bottom: 30px !important; -} - -p { - margin: 0 0 20px; -} - -pre { - margin: 0 0 20px; -} - - -header { - width: 940px; - margin: 20px auto 30px; -} - -header a { - display: inline-block; - padding: 5px; - font-weight: bold; - font-size: 24px; - color: #222; - text-decoration: none; - text-shadow: 0 1px 0 white; -} - -header a:hover { - text-decoration: none; -} - -footer { - width: 750px; - padding-left: 190px; - margin: 40px auto 60px; - font-size: 12px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); -} - -footer ul { - display: inline; - padding: 0; -} - -footer ul li { - display: inline; - padding-right: 15px; - list-style: none; -} - -footer ul li a { - color: #999; - text-decoration: none; -} - -footer ul li a:hover { - color: #222; -} - -footer .right { - float: right; -} - -.container { - position: relative; - width: 940px; - margin: 0 auto; - padding-top: 25px; -} - -.container > nav { - position: absolute; - z-index: 20; - width: 190px; - left: 0; - top: 24px; - bottom: 4px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.65); - background: #EEE; - background: rgba(255, 255, 255, 0.25); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6),0 0 1px 1px rgba(80, 84, 92, 0.1),0 1px 2px rgba(80, 84, 92, 0.2); -} - -.container > nav > ul { - padding: 0; - margin: 0; -} - -.container > nav > ul > li { - padding: 0; - margin: 0; - list-style: none; -} - -.container > nav > ul > li > span { - text-transform: uppercase; - font-size: 11px; - font-weight: bold; - color: #9CA5B5; - display: block; - margin: 20px 0 4px 0; - padding: 0 25px; -} - -.container > nav > ul ul { - padding: 0; - margin: 0; -} - -.container > nav > ul ul > li { - list-style: none; -} - -.container > nav > ul ul > li > a { - margin-bottom: 1px; - font-size: 12px; - height: 22px; - line-height: 22px; - text-decoration: none; - color: #0068DE; - display: block; - padding: 1px 20px 1px 40px; -} -.container > nav > ul ul > li > a:hover { - border-top: 1px solid rgba(156, 165, 181, 0.25); - border-bottom: 1px solid #CACDD3; - box-shadow: 1px 0 rgba(156, 165, 181, 0.08); - background: transparent; - background: -webkit-linear-gradient(white, #F0F2F5); - background: -moz-linear-gradient(white, #F0F2F5); - background: -ms-linear-gradient(white, #F0F2F5); - background: -o-linear-gradient(white, #F0F2F5); - background: linear-gradient(white, #F0F2F5); - padding: 0 20px 0 40px; -} - -.container > nav > ul ul > li.selected > a { - padding: 0 20px 0 40px; - border-top: 1px solid #394755; - border-bottom: 1px solid #1a2026; - box-shadow: 0 -1px 0 rgba(17, 46, 97, 0.03),0 1px 0 rgba(17, 46, 97, 0.15); - background: #2e3945; - background: transparent; - background: -webkit-linear-gradient(#4f6275, #2e3945); - background: -moz-linear-gradient(#4f6275, #2e3945); - background: -ms-linear-gradient(#4f6275, #2e3945); - background: -o-linear-gradient(#4f6275, #2e3945); - background: linear-gradient(#4f6275, #2e3945); - font-weight: 700; - color: white; - box-shadow: inset 0 1px 0 #678099; - text-shadow: 0 -1px 0 rgba(26, 32, 38, 0.75); -} - -.container > section { - position: relative; - z-index: 30; - background: white; - background: #fff; - margin-left: 190px; - box-shadow: 0 0 1px 1px rgba(80, 84, 92, 0.1),0 1px 2px rgba(80, 84, 92, 0.5); - padding: 20px 30px; - min-height: 800px; -} - -.container.nosidebar { - width: auto; - padding: 20px; -} - -.container > .nosidebar { - margin-left: 0; -} - -section ul, section ol { - list-style-position: outside; -} section ul li { list-style-type: square; @@ -245,281 +5,6 @@ section ul li { margin-top: 4px; } -/* copy from bootstrap - applied without class attribute */ -table { - width: 100%; - margin-bottom: 20px; -} -table th, -table td { - padding: 8px; - line-height: 20px; - text-align: left; - vertical-align: top; - border-top: 1px solid #dddddd; -} -table th { - font-weight: bold; -} -table thead th { - vertical-align: bottom; -} -table caption + thead tr:first-child th, -table caption + thead tr:first-child td, -table colgroup + thead tr:first-child th, -table colgroup + thead tr:first-child td, -table thead:first-child tr:first-child th, -table thead:first-child tr:first-child td { - border-top: 0; -} -table tbody + tbody { - border-top: 2px solid #dddddd; -} - -table { - border: 1px solid #dddddd; - border-collapse: separate; - *border-collapse: collapse; - border-left: 0; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} -table th, -table td { - border-left: 1px solid #dddddd; -} -table caption + thead tr:first-child th, -table caption + tbody tr:first-child th, -table caption + tbody tr:first-child td, -table colgroup + thead tr:first-child th, -table colgroup + tbody tr:first-child th, -table colgroup + tbody tr:first-child td, -table thead:first-child tr:first-child th, -table tbody:first-child tr:first-child th, -table tbody:first-child tr:first-child td { - border-top: 0; -} -table thead:first-child tr:first-child th:first-child, -table tbody:first-child tr:first-child td:first-child { - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topleft: 4px; -} -table thead:first-child tr:first-child th:last-child, -table tbody:first-child tr:first-child td:last-child { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -moz-border-radius-topright: 4px; -} -table thead:last-child tr:last-child th:first-child, -table tbody:last-child tr:last-child td:first-child, -table tfoot:last-child tr:last-child td:first-child { - -webkit-border-radius: 0 0 0 4px; - -moz-border-radius: 0 0 0 4px; - border-radius: 0 0 0 4px; - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; -} -table thead:last-child tr:last-child th:last-child, -table tbody:last-child tr:last-child td:last-child, -table tfoot:last-child tr:last-child td:last-child { - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-bottomright: 4px; -} -table caption + thead tr:first-child th:first-child, -table caption + tbody tr:first-child td:first-child, -table colgroup + thead tr:first-child th:first-child, -table colgroup + tbody tr:first-child td:first-child { - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topleft: 4px; -} -table caption + thead tr:first-child th:last-child, -table caption + tbody tr:first-child td:last-child, -table colgroup + thead tr:first-child th:last-child, -table colgroup + tbody tr:first-child td:last-child { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -moz-border-radius-topright: 4px; -} -table tbody tr:nth-child(odd) td, -table tbody tr:nth-child(odd) th { - background-color: #f9f9f9; -} -table tbody tr:hover td, -table tbody tr:hover th { - background-color: #f5f5f5; -} - -table p { - margin: 0; -} - -.header { - background-color: #394755; - height: 76px; - -webkit-box-shadow: -5px 6px 15px -5px rgba(0, 0, 0, 0.50); - box-shadow: -5px 6px 15px -5px rgba(0, 0, 0, 0.50); - -webkit-box-shadow: -5px 6px 15px -5px rgba(0, 0, 0, 0.50); - -moz-box-shadow: -5px 6px 15px -5px rgba(0, 0, 0, 0.50); - -o-box-shadow: -5px 6px 15px -5px rgba(0, 0, 0, 0.50); -} - -.header-container { - max-width: 968px; - margin: 0 auto; -} - -.header-container ul { - float: right; - margin-top: 25px; -} - -.header-container ul li { - display: inline; - margin: 20px; - font-size: 16px; - font-weight: 600; - list-style: none; -} - -.header-container ul li a { - color: #fff; - text-decoration: none; -} - -.header-container ul li a:hover { - background-color: #394755; - color: #eee; -} - -.header h1 { - float: left; - margin: 0; - letter-spacing: 1px; - font-weight: 800; - margin-top: 16px; - font-size: 32px; - color: #fff; - text-shadow: 0px 2px 3px #666; -} - -.header .logo { - background: url("/img/logo-small.png") 0 50% no-repeat; - width: 40px; - height: 76px; - float: left; - margin-left: 10px; -} - -.main-container { - max-width: 968px; - margin: 0 auto; - padding-top: 20px; -} - -.navbar-fixed-top { - margin-bottom: 0; -} -.navbar-inner { - border-bottom: 0 none; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); - filter: none; - padding: 10px 0; -} -.navbar .brand { - font-family: 'Open Sans', Helvetica, Arial, sans-serif; - font-size: 34px; - font-weight: 800; - letter-spacing: 1px; - -} -.navbar .nav > li > a { - font-size: 16px; - font-weight: normal; - text-shadow: none; - -webkit-transition: all .2s linear; - -moz-transition: all .2s linear; - -ms-transition: all .2s linear; - -o-transition: all .2s linear; - transition: all .2s linear; -} -.navbar .nav > li > a:hover { - -moz-transform: translateY(-5px); - -webkit-transform: translateY(-5px); - -o-transform: translateY(-5px); - -ms-transform: translateY(-5px); - transform: translateY(-5px); -} -.navbar .nav > li.active > a, -.navbar .nav > li.active:hover > a, -.navbar .nav > .active > a, -.navbar .nav > .active > a:hover, -.navbar .nav > .active > a:focus { - padding-bottom: 4px; - -webkit-box-shadow: none; - box-shadow: none; - -moz-box-shadow: none; -} -.btn-dropnav { - float: right; - margin-left: 5px; - margin-right: 5px; - padding: 7px 10px; - display: none; -} -.btn-dropnav .icon-bar { - background-color: #F5F5F5; - -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); - -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); - display: block; - height: 2px; - width: 18px; -} -.btn-dropnav .icon-bar + .icon-bar { - margin-top: 3px; -} - -.navbar-inner { - background: none repeat scroll 0 0 #394755; -} -.navbar .brand { - color: #FFFFFF; -} -.navbar .nav > li > a { - color: #D6DFE5; -} -.navbar .nav > li > a:hover { - color: #FFFFFF; -} -.navbar .nav > li.active > a, -.navbar .nav > li.active:hover > a, -.navbar .nav > .active > a, -.navbar .nav > .active > a:hover, -.navbar .nav > .active > a:focus { - background: none repeat scroll 0 0 transparent; - border-bottom: 2px solid #308CCF; - color: #FFFFFF; -} - - -pre code { - white-space: pre; -} - -pre { - overflow: auto; - word-break: normal; - word-wrap: normal; - white-space: pre; -} - h2.current-step { color: #0088cc; font-size: 20px; @@ -602,13 +87,17 @@ h2.step-finished img { } .browser iframe { - border-radius: 0 0 5px 5px; + border-radius: 0 0 5px 5px; border: 0; width: 100%; height: 500px; display: block; } +.browser .login-widget2 iframe { + height: 650px; +} + .browser-toolbar { border-bottom: 1px solid #ddd; padding: 6px; @@ -641,13 +130,14 @@ h2.step-finished img { } .edit-fiddle { - color: #0088cc; + background: url("https://cdn.auth0.com/img/codepen-Black-Large.png") no-repeat scroll; + color: #000000; + cursor: pointer; + font-weight: bold; + height: 35px; + width: 106px; + background-size: cover; border: none; - font-family: 'Open Sans', Helvetica,Arial,sans-serif; - padding-left: 35px; - background: url("/img/logo-fiddle.png") 5% 40% no-repeat; - margin-right: 15px; - font-size: 13px; } .edit-fiddle:hover { @@ -661,20 +151,6 @@ a.anchor { text-decoration: none; } -h2,h3,h4 { - left: -20px; - position: relative; -} - -h2:hover a.anchor{ - visibility: visible; -} -h3:hover a.anchor{ - visibility: visible; -} -h4:hover a.anchor{ - visibility: visible; -} .fixit { position: fixed; @@ -682,6 +158,10 @@ h4:hover a.anchor{ margin-left: 18px; margin-bottom: 5px; font-size: 12px; + border: 1px solid #ccc; + border-radius: 5px; + background-color: rgba(255, 255, 255, 0.5); + padding: 5px; } .fixit i { diff --git a/themes/default/public/css/docs.css b/themes/default/public/css/docs.css new file mode 100644 index 00000000..97c8db2e --- /dev/null +++ b/themes/default/public/css/docs.css @@ -0,0 +1,603 @@ +.btn-package { + white-space: normal; + margin-bottom: 0px; +} +div.package span:first-child { + display: block; +} +div.package span { + color: #fff; +} +div.package a.btn-package { + line-height: 20px; +} +div.package span.smaller { + font-size: 10px; +} +.wpf-winforms-tutorial i { + border-color: #ffc0cb; +} +.sub-navbar { + background: #5d676f; + border-radius: 3px; + margin-bottom: 40px; + font-weight: bold; + font-size: 12px; + color: #fff; + min-height: 44px; +} +.sub-navbar .navbar-text { + color: #fff; + display: inline-block; + vertical-align: middle; + font-weight: bold; + font-size: 12px; + color: #fff; + margin: 6px 15px; + line-height: 32px; +} +.sub-navbar .dropdown { + display: inline-block; + height: 100%; + margin: 0px; + padding-left: 0px; +} +.sub-navbar .dropdown .caret { + position: absolute; + right: 20px; + top: 20px; +} +.sub-navbar .dropdown .btn.dropdown-toggle { + color: #fff; + min-width: 226px; + line-height: 32px; + font-size: 12px; + background: none; + border: 0; + border-radius: 0; + border-right: 1px solid #ccc; + text-transform: none; + background: linear-gradient(180deg, transparent 30%, rgba(0,0,0,0.8) 100%); + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + letter-spacing: 0.5px; + position: relative; + text-align: left; + padding-left: 20px; + padding-right: 40px; + letter-spacing: 0.5px; + display: inline-block; + height: 100%; +} +.sub-navbar .dropdown .btn.dropdown-toggle:focus, +.sub-navbar .dropdown .btn.dropdown-toggle:active { + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; + -webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; + -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; +} +.sub-navbar .dropdown .btn.dropdown-toggle:hover { + -webkit-background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.8) 100%); + -o-background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.8) 100%); + background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.8) 100%); +} +.sub-navbar .dropdown .dropdown-menu { + min-width: 226px; +} +.sub-navbar .navbar-form { + margin: 5px 10px; + padding: 0; +} +.sub-navbar .navbar-form:last-child { + margin: 5px 10px; +} +.sub-navbar .navbar-form input { + min-width: 180px; +} +.sub-navbar .navbar-form input, +.sub-navbar .navbar-form button { + -webkit-box-shadow: none; + box-shadow: none; +} +.sub-navbar .navbar-form input:focus, +.sub-navbar .navbar-form button:focus, +.sub-navbar .navbar-form input:active, +.sub-navbar .navbar-form button:active { + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; + -webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; + -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; +} +.sub-navbar .navbar-form .btn { + font-size: 14px; + padding-left: 10px; + padding-right: 10px; +} +.sub-navbar .navbar-form .btn i { + font-size: 14px; + line-height: 14px; + display: inline-block; + vertical-align: middle; +} +.page-docs #tutorial-navigator .step.select-app-type { + padding: 20px 4px; +} +.page-docs #tutorial-navigator .step.select-app-type .app-type .app-type-item strong.label { + padding: 0; +} +.page-docs .installers { + width: 550px; + margin-bottom: 20px; + display: table; + padding: 10px; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.page-docs .installers ul { + width: auto; + text-align: center; + margin: 0 auto; + display: table-row; +} +.page-docs .installers ul img { + display: block; + margin: 0 auto; +} +.page-docs .installers ul a { + display: block; + width: 100%; + text-decoration: none; + font-size: 16px; + padding-top: 10px; + padding-bottom: 10px; +} +.page-docs .installers ul a:hover, +.page-docs .installers ul a:active { + background: #e1e1e8; + color: #005580; +} +.page-docs .installers ul li { + width: 33%; + display: table-cell; +} +.page-docs .installers a small { + font-size: 10px; + display: block; + color: #888; +} +.page-docs .btn-mid { + font-size: 14px; + font-weight: bold; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} +.page-docs .browser { + border: 1px solid #ddd; + border-radius: 5px; + margin: 10px 0 0 0; +} +.page-docs .browser iframe { + border-radius: 0 0 5px 5px; + border: 0; + width: 100%; + height: 500px; + display: block; +} +.page-docs .browser .login-widget2 iframe { + height: 650px; +} +.page-docs .browser-toolbar { + border-bottom: 1px solid #ddd; + padding: 6px; +} +.page-docs .browser-icons { + float: left; + padding: 8px 0 0 0; + margin-left: 10px; +} +.page-docs .browser-icons i { + margin-right: 10px; + color: #14204d; +} +.page-docs .browser-address { + background: #fff; + border: 1px solid #ddd; + border-radius: 3px; + margin-left: 90px; + padding: 4px; +} +.page-docs .browser-address i { + top: 3px; + position: relative; + opacity: 0.5; + left: 2px; +} +.page-docs .browser-content { + background: #fff; + border-radius: 0 0 5px 5px; +} +.page-docs .edit-fiddle { + background: url("https://cdn.auth0.com/img/codepen-Black-Large.png") no-repeat scroll; + color: #000; + cursor: pointer; + font-weight: bold; + height: 35px; + width: 106px; + background-size: cover; + border: none; +} +.page-docs .edit-fiddle:hover { + color: #005580; + text-decoration: underline; +} +.page-docs a.anchor { + margin-left: -24px; + visibility: hidden; + text-decoration: none; + position: relative; +} +.page-docs a.anchor i { + font-size: 14px; + width: 24px; + display: inline-block; +} +.page-docs h2:hover a.anchor { + visibility: visible; +} +.page-docs h3:hover a.anchor { + visibility: visible; +} +.page-docs h4:hover a.anchor { + visibility: visible; +} +.page-docs .widget-tut .snippet-header { + background-color: #8a8a8a; + padding: 7px; + color: #fff; + border-radius: 2px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + margin-top: 5px; +} +.page-docs .widget-tut pre { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.page-docs #widget-chooser { + width: 400px; +} +.page-docs h1, +.page-docs h2, +.page-docs h3, +.page-docs h4, +.page-docs h5 { + margin-top: 20px; + margin-bottom: 20px; +} +.page-docs h1 i, +.page-docs h2 i, +.page-docs h3 i, +.page-docs h4 i, +.page-docs h5 i { + display: none; +} +.page-docs h1 { + margin-top: 0; +} +.page-docs p, +.page-docs li { + color: #4d4d4d; +} +.page-docs pre { + border: 0; + padding: 20px; + font-size: 13px; + line-height: 20px; + margin-bottom: 20px; + word-wrap: normal; +} +.page-docs pre code { + white-space: pre; +} +.page-docs pre::-webkit-scrollbar { + width: 12px; +} +.page-docs pre::-webkit-scrollbar-track { + background: #000; + background: rgba(0,0,0,0.2); +} +.page-docs pre::-webkit-scrollbar-thumb { + background: #fff; + background: rgba(255,255,255,0.5); +} +.page-docs .sidebar-docs ul { + margin: 0; + padding: 0; + max-width: 230px; + list-style: none; +} +.page-docs .sidebar-docs ul li.box { + border: 1px solid #eee; + border-radius: 3px; + margin-bottom: 20px; + padding: 14px 20px; +} +.page-docs .sidebar-docs ul li.box strong { + text-transform: uppercase; + font-weight: bold; + font-size: 12px; + margin-bottom: 6px; + display: block; + letter-spacing: 1px; + color: #999; +} +.page-docs .sidebar-docs ul li.box a { + color: #666; +} +.page-docs .sidebar-docs ul li.box a:hover { + color: #000; +} +.page-docs .sidebar-docs ul li.box.box-terminal, +.page-docs .sidebar-docs ul li.box.box-mobile-phone, +.page-docs .sidebar-docs ul li.box.box-html5, +.page-docs .sidebar-docs ul li.box.box-laptop { + background: #f9f9f9; + border-color: transparent; +} +.page-docs .sidebar-docs ul li.box.box-terminal, +.page-docs .sidebar-docs ul li.box.box-mobile-phone { + margin-bottom: 0; + padding-bottom: 0; +} +.page-docs .sidebar-docs ul li.box.box-mobile-phone { + border-radius: 0; +} +.page-docs .sidebar-docs ul li.box.box-html5 { + border-radius: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.page-docs .sidebar-docs ul li { + line-height: 28px; +} +.page-docs .sidebar-docs ul li i:before { + content: "\e16f"; + font-family: "budicon-font" !important; + speak: none; +} +.page-docs .sidebar-docs ul li i { + width: 10px; + height: 10px; + display: inline-block; + font-family: "budicon-font" !important; + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + margin-right: 8px; + position: relative; + top: 2px; + height: 14px; +} +.page-docs .sidebar-docs ul li.selected a { + color: #000 !important; +} +.page-docs .sidebar-docs li.box-laptop:before { + text-transform: uppercase; + font-weight: bold; + font-size: 12px; + margin-bottom: 6px; + display: block; + letter-spacing: 1px; + color: #333; +} +.page-docs .sidebar-docs li.box-laptop ul li i, +.page-docs .sidebar-docs li.box-mobile-phone ul li i { + border: 2px solid #666; + height: 10px; + border-radius: 100px; + position: relative; + position: static; +} +.page-docs .sidebar-docs li.box-laptop ul li i:before, +.page-docs .sidebar-docs li.box-mobile-phone ul li i:before { + content: none; +} +.page-docs .sidebar-docs li.box-laptop ul li.nodejs-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.nodejs-tutorial i { + border-color: #a9d353; +} +.page-docs .sidebar-docs li.box-laptop ul li.aspnet-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.aspnet-tutorial i { + border-color: #4ea5c7; +} +.page-docs .sidebar-docs li.box-laptop ul li.aspnet-owin-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.aspnet-owin-tutorial i { + border-color: #4e79c7; +} +.page-docs .sidebar-docs li.box-laptop ul li.azure-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.azure-tutorial i { + border-color: #684ec7; +} +.page-docs .sidebar-docs li.box-laptop ul li.servicestack-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.servicestack-tutorial i { + border-color: #a84ec7; +} +.page-docs .sidebar-docs li.box-laptop ul li.php-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.php-tutorial i { + border-color: #cf5191; +} +.page-docs .sidebar-docs li.box-laptop ul li.rails-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.rails-tutorial i { + border-color: #e35e59; +} +.page-docs .sidebar-docs li.box-laptop ul li.ios-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.ios-tutorial i { + border-color: #e39359; +} +.page-docs .sidebar-docs li.box-laptop ul li.android-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.android-tutorial i { + border-color: #e3ae59; +} +.page-docs .sidebar-docs li.box-laptop ul li.windowsphone-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.windowsphone-tutorial i { + border-color: #e3ca59; +} +.page-docs .sidebar-docs li.box-laptop ul li.xamarin-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.xamarin-tutorial i { + border-color: #dfe158; +} +.page-docs .sidebar-docs li.box-laptop ul li.win8-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.win8-tutorial i { + border-color: #4ec75c; +} +.page-docs .sidebar-docs li.box-laptop ul li.win8-cs-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.win8-cs-tutorial i { + border-color: #f00; +} +.page-docs .sidebar-docs li.box-laptop ul li.wpf-winforms-tutorial i, +.page-docs .sidebar-docs li.box-mobile-phone ul li.wpf-winforms-tutorial i { + border-color: #ffc0cb; +} +.page-docs .sidebar-docs li.box-mobile-phone ul li i { + border-radius: 0; +} +.page-docs section.content h1 { + margin-top: 0; +} +.page-docs section.content li, +.page-docs section.content p { + line-height: 26px; + font-size: 15px; +} +.page-docs section.content p { + margin-bottom: 20px; +} +.page-docs section.content ul, +.page-docs section.content ol { + padding: 0; + padding-left: 18px; + margin: 0; + margin-bottom: 20px; +} +.page-docs section.content blockquote { + background: #ecfafe; + border: 0; + text-align: center; + color: #fff; + padding: 30px 40px; + margin-bottom: 20px; +} +.page-docs section.content blockquote p { + line-height: 30px; + color: #14204d; + margin: 0; +} +.page-docs section.content blockquote.warning { + background: red; +} +.page-docs section.content blockquote.warning p { + color: white; + font-weight: bold; +} +.page-docs section.content blockquote.warning a { + color: white; + text-decoration: underline; +} +.page-docs section.content img { + max-width: 100%; + display: block; + margin: auto; +} +.page-docs .getting-started-logos { + margin-bottom: 30px; + margin-top: 30px; + overflow: hidden; +} +.page-docs .logo-tutorial { + background-color: #f1f1f1; + text-align: center; + border-radius: 3px; + display: block; + padding: 10px; +} +.page-docs .logo-tutorial strong { + background-color: #fff; + display: block; + padding: 10px 0; + border-radius: 3px; +} +.page-docs .logo-tutorial strong img { + height: 45px; + max-width: 100%; +} +.page-docs .logo-tutorial span { + text-transform: uppercase; + font-weight: bold; + display: block; + margin-top: 6px; + font-size: 11px; + letter-spacing: 1px; + color: #333; +} +.page-docs .fixit { + display: block; + margin-top: 20px; + text-align: right; + padding-top: 20px; + border-top: 1px solid #eee; + color: #666; +} +.page-docs .fixit a { + font-weight: bold; +} +.page-docs form#search .form-control { + height: 32px; +} +.page-docs form#search button, +.page-docs form#search input { + border: 0; + margin-top: 1px; + font-weight: normal; +} +.page-docs form#search button { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.page-docs form#search input { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.page-docs form#search .btn.btn-default:hover, +.page-docs form#search a.btn.btn-default:hover { + background: #fff; +} +.page-docs #search-results .st-search-summary { + margin-bottom: 20px; +} +.page-docs #search-results .st-results h3 { + font-size: 20px; + margin-bottom: 10px; +} +.page-docs #search-results .st-results .st-result { + padding-bottom: 20px; + margin-bottom: 20px; +} +.page-docs #search-results .st-results .st-result .st-snippet em { + font-weight: bold; + background-color: rgba(255,242,51,0.298); +} +.page-docs #search-results .st-results .st-result.final { + margin-bottom: 10px; +} +.page-docs #search-results .st-pagination { + text-align: center; +} \ No newline at end of file diff --git a/themes/default/public/css/docs.styl b/themes/default/public/css/docs.styl new file mode 100644 index 00000000..b236272d --- /dev/null +++ b/themes/default/public/css/docs.styl @@ -0,0 +1,621 @@ +color_blue = #14204d; +color_blue_light = #3cc8f4; +color_red = #eb5422; +color_grey = #d1d2d4; + +color_node_js = #a9d353; +color_asp= #4ea5c7; +color_aspnet_owin= #4e79c7; +color_azure= #684ec7; +color_servicestack= #a84ec7; +color_php= #cf5191; +color_rails= #e35e59; +color_ios= #e39359; +color_android= #e3ae59; +color_windowsphone= #e3ca59; +color_xamarin= #dfe158; +color_win8= #4ec75c; +color_win8_cs= #f00; + +.btn-package { + white-space: normal; + margin-bottom: 0px; +} + +div.package span:first-child { + display: block; +} + +div.package span { + color: white; +} + +div.package a.btn-package { + line-height: 20px; +} + +div.package span.smaller { + font-size: 10px; +} + +&.wpf-winforms-tutorial i + border-color: #ffc0cb; + +.sub-navbar + background: #5d676f; + border-radius: 3px; + margin-bottom: 40px; + font-weight: bold; + font-size: 12px; + color: #fff; + min-height: 44px; + + .navbar-text + color: white; + display: inline-block; + vertical-align: middle; + font-weight: bold; + font-size: 12px; + color: #fff; + margin: 6px 15px; + line-height: 32px; + + .dropdown + display: inline-block; + height: 100%; + margin: 0px; + padding-left: 0px; + .caret + position: absolute; + right: 20px; + top: 20px; + .btn.dropdown-toggle + color: #fff; + min-width: 226px; + line-height: 32px; + font-size: 12px; + background: none; + border: 0; + border-radius: 0; + border-right: 1px solid #ccc; + text-transform: none; + background: linear-gradient(180deg, transparent 30%, rgba(0,0,0,0.8) 100%); + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + letter-spacing: 0.5px; + position: relative; + text-align: left; + padding-left: 20px; + padding-right: 40px; + letter-spacing: 0.5px; + display: inline-block; + height: 100%; + &:focus, &:active + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; + -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + &:hover + -webkit-background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.8) 100%); + -o-background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.8) 100%); + background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.8) 100%); + + .dropdown-menu + min-width: 226px; + + .navbar-form + margin: 5px 10px; + padding: 0; + &:last-child + margin: 5px 10px; + input + min-width: 180px; + input, button + -webkit-box-shadow: none; + box-shadow: none; + &:focus, &:active + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; + -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + + .btn + font-size: 14px; + padding-left: 10px; + padding-right: 10px; + i + font-size: 14px; + line-height: 14px; + display: inline-block; + vertical-align: middle; + +.page-docs + + #tutorial-navigator .step.select-app-type + padding: 20px 4px; + .app-type .app-type-item strong.label + padding: 0; + + + .installers { + width: 550px; + margin-bottom: 20px; + display: table; + padding: 10px; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + } + + .installers ul { + width: auto; + text-align: center; + margin: 0 auto; + display: table-row; + } + + .installers ul img { + display: block; + margin: 0 auto; + } + + .installers ul a { + display: block; + width: 100%; + text-decoration: none; + font-size: 16px; + padding-top: 10px; + padding-bottom: 10px; + } + + .installers ul a:hover, + .installers ul a:active { + background: #e1e1e8; + color: #005580; + } + + .installers ul li { + width: 33%; + display: table-cell; + } + + .installers a small { + font-size: 10px; + display: block; + color: #888; + } + + .btn-mid { + font-size: 14px; + font-weight: bold; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + } + + + .browser { + border: 1px solid #ddd; + border-radius: 5px; + margin: 10px 0 0 0; + } + + .browser iframe { + border-radius: 0 0 5px 5px; + border: 0; + width: 100%; + height: 500px; + display: block; + } + + .browser .login-widget2 iframe { + height: 650px; + } + + .browser-toolbar { + border-bottom: 1px solid #ddd; + padding: 6px; + } + + .browser-icons { + float: left; + padding: 8px 0 0 0; + margin-left: 10px; + } + + .browser-icons i { + margin-right: 10px; + color: color_blue; + } + + .browser-address { + background: #ffffff; + border: 1px solid #ddd; + border-radius: 3px; + margin-left: 90px; + padding: 4px; + } + .browser-address i { + top: 3px; + position: relative; + opacity: .5; + left: 2px; + } + + .browser-content { + background: #ffffff; + border-radius: 0 0 5px 5px; + } + + .edit-fiddle { + background: url("https://cdn.auth0.com/img/codepen-Black-Large.png") no-repeat scroll; + color: #000000; + cursor: pointer; + font-weight: bold; + height: 35px; + width: 106px; + background-size: cover; + border: none; + } + + .edit-fiddle:hover { + color: #005580; + text-decoration: underline; + } + + a.anchor + margin-left: -24px; + visibility: hidden; + text-decoration: none; + position: relative; + + i + font-size: 14px; + width: 24px; + display: inline-block + + &:hover + visibility + + h2:hover a.anchor{ + visibility: visible; + } + h3:hover a.anchor{ + visibility: visible; + } + h4:hover a.anchor{ + visibility: visible; + } + + + .widget-tut .snippet-header { + background-color: #8A8A8A; + padding: 7px; + color: white; + border-radius: 2px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + margin-top: 5px; + } + + .widget-tut pre { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + #widget-chooser { + width: 400px; + } + + + h1, h2,h3,h4,h5 + margin-top: 20px; + margin-bottom: 20px; + i + display: none + + h1 + margin-top: 0; + + p,li + color: #4d4d4d; + + pre + border: 0; + padding: 20px; + font-size: 13px; + line-height: 20px; + margin-bottom: 20px; + word-wrap: normal; + + code + white-space: pre; + + &::-webkit-scrollbar + width: 12px; + + &::-webkit-scrollbar-track + background: black; + background: rgba(0,0,0, 0.2); + + &::-webkit-scrollbar-thumb + background: white; + background: rgba(255,255,255, 0.5); + + .sidebar-docs + ul + margin: 0; + padding: 0; + max-width: 230px; + list-style: none; + li.box + border: 1px solid #eee; + border-radius: 3px; + margin-bottom: 20px; + padding: 14px 20px; + strong + text-transform: uppercase + font-weight: bold; + font-size: 12px; + margin-bottom: 6px; + display: block + letter-spacing: 1px; + color: #999; + a + color: #666; + + &:hover + color: black; + + &.box-terminal, &.box-mobile-phone, &.box-html5, &.box-laptop + background: #f9f9f9; + border-color: transparent; + + &.box-terminal, &.box-mobile-phone + margin-bottom: 0; + padding-bottom: 0; + + &.box-mobile-phone + border-radius: 0; + + &.box-html5 + border-radius: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; + + ul + li + line-height: 28px; + + i:before + content: "\e16f"; + font-family: "budicon-font" !important; + speak: none; + i + width: 10px; + height: 10px; + display: inline-block + font-family: "budicon-font" !important; + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + margin-right: 8px; + position: relative; + top: 2px; + height: 14px; + // background: red; + + &.selected + a + color: black !important; + // font-weight: bold; + + + + li.box-laptop:before + text-transform: uppercase + font-weight: bold; + font-size: 12px; + margin-bottom: 6px; + display: block + letter-spacing: 1px; + color: #333 + + li.box-laptop, li.box-mobile-phone + + ul + li + i + border: 2px solid #666; + height: 10px; + border-radius: 100px; + position: relative + position: static; + &:before + content: none; + + + &.nodejs-tutorial i + border-color: #a9d353; + + &.aspnet-tutorial i + border-color: #4ea5c7; + + &.aspnet-owin-tutorial i + border-color: #4e79c7; + + &.azure-tutorial i + border-color: #684ec7; + + &.servicestack-tutorial i + border-color: #a84ec7; + + &.php-tutorial i + border-color: #cf5191; + + &.rails-tutorial i + border-color: #e35e59; + + &.ios-tutorial i + border-color: #e39359; + + + &.android-tutorial i + border-color: #e3ae59; + + + &.windowsphone-tutorial i + border-color: #e3ca59; + + + &.xamarin-tutorial i + border-color: #dfe158; + + + &.win8-tutorial i + border-color: #4ec75c; + + + &.win8-cs-tutorial i + border-color: #f00; + + + &.wpf-winforms-tutorial i + border-color: #ffc0cb; + + + + li.box-mobile-phone + ul + li + i + border-radius: 0; + + + + section.content + h1 + margin-top: 0; + + li, p + line-height: 26px; + font-size: 15px; + + p + margin-bottom: 20px; + + ul, ol + padding: 0; + padding-left: 18px; + margin: 0; + margin-bottom: 20px; + + blockquote + background: lighten(color_blue_light, 90%); + border: 0; + text-align: center + color: white; + padding: 30px 40px; + margin-bottom: 20px; + p + line-height: 30px; + color: color_blue + margin: 0; + + blockquote.warning + background: red; + p + color: white; + font-weight: bold; + a + color: white; + text-decoration: underline; + + img + max-width: 100%; + display: block; + margin: auto; + + .getting-started-logos + margin-bottom: 30px; + margin-top: 30px; + overflow: hidden + + .logo-tutorial + background-color: #f1f1f1; + text-align: center + border-radius: 3px; + display: block + padding: 10px; + strong + background-color: white; + display: block + padding: 10px 0; + border-radius: 3px; + img + height: 45px; + max-width: 100%; + + + span + text-transform: uppercase + font-weight: bold + display: block + margin-top: 6px; + font-size: 11px; + letter-spacing: 1px; + color: #333; + + .fixit + display: block + margin-top: 20px; + text-align: right + padding-top: 20px; + border-top: 1px solid #eee; + color: #666; + a + font-weight: bold + + form#search + .form-control + height: 32px; + button, input + border: 0; + margin-top: 1px; + font-weight: normal + button + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + input + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + + .btn.btn-default:hover, a.btn.btn-default:hover + background: #fff; + + #search-results + .st-search-summary + margin-bottom: 20px; + + .st-results + h3 + font-size: 20px; + margin-bottom: 10px; + .st-result + padding-bottom: 20px; + margin-bottom: 20px; + .st-snippet + em + font-weight: bold; + background-color: rgba(255,242,51,.29804); + &.final + margin-bottom: 10px; + .st-pagination + text-align: center; diff --git a/themes/default/public/js/jquery.ba-hashchange.min.js b/themes/default/public/js/jquery.ba-hashchange.min.js new file mode 100644 index 00000000..751c17f8 --- /dev/null +++ b/themes/default/public/js/jquery.ba-hashchange.min.js @@ -0,0 +1,12 @@ +/* + * jQuery hashchange event - v1.3 - 7/21/2010 + * http://benalman.com/projects/jquery-hashchange-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function($,window,undefined){'$:nomunge';var str_hashchange='hashchange',doc=document,fake_onhashchange,special=$.event.special,doc_mode=doc.documentMode,supports_onhashchange='on'+str_hashchange in window&&(doc_mode===undefined||doc_mode>7);function get_fragment(url){url=url||location.href;var index=url.indexOf('#');return index===-1?'#':url.substr(index);};$.fn[str_hashchange]=function(fn){return fn?this.bind(str_hashchange,fn):this.trigger(str_hashchange);};$.fn[str_hashchange].delay=50;special[str_hashchange]=$.extend(special[str_hashchange],{setup:function(){if(supports_onhashchange){return false;} +$(fake_onhashchange.start);},teardown:function(){if(supports_onhashchange){return false;} +$(fake_onhashchange.stop);}});fake_onhashchange=(function(){var self={},timeout_id,last_hash=get_fragment(),fn_retval=function(val){return val;},history_set=fn_retval,history_get=fn_retval;self.start=function(){timeout_id||poll();};self.stop=function(){timeout_id&&clearTimeout(timeout_id);timeout_id=undefined;};function poll(){var hash=get_fragment(),history_hash=history_get(last_hash);if(hash!==last_hash){history_set(last_hash=hash,history_hash);$(window).trigger(str_hashchange);}else if(history_hash!==last_hash){location.href=location.href.replace(/#.*/,'')+history_hash;} +timeout_id=setTimeout(poll,$.fn[str_hashchange].delay);};window.attachEvent&&!window.addEventListener&&!supports_onhashchange&&(function(){var iframe,iframe_src;self.start=function(){if(!iframe){iframe_src=$.fn[str_hashchange].src;iframe_src=iframe_src&&iframe_src+get_fragment();iframe=$('