├── .gitignore ├── LICENSE ├── docs └── media │ ├── giftdeliveries-client.png │ ├── giftdeliveries-grants.png │ ├── machine-to-machine-samples.png │ ├── worldmappers-rs.png │ └── worldmappers-scopes.png ├── machine-to-machine ├── README.md ├── giftdeliveries-nodejs │ ├── config.json │ ├── index.js │ ├── lib │ │ ├── env.js │ │ └── logger.js │ └── package.json └── worldmappers-api-nodejs │ ├── config.json │ ├── lib │ ├── env.js │ ├── getPublicKey.js │ ├── logger.js │ └── middleware │ │ └── requireScope.js │ ├── package.json │ └── server.js └── user-consent ├── README.md ├── calendarapp-authz-code-nodejs ├── .deployment ├── README.md ├── config.json ├── deploy.sh ├── logger.js ├── package.json ├── server.js └── views │ ├── account.hbs │ ├── appointments.hbs │ ├── contacts.hbs │ ├── index.hbs │ ├── layout │ └── default.hbs │ └── tasks.hbs ├── calendarapp-implicit-jquery ├── .deployment ├── README.MD ├── config.json ├── deploy.sh ├── logger.js ├── package.json ├── server.js └── views │ └── index.hbs ├── organizer-aspnet ├── Api.csproj ├── AppointmentsController.cs ├── ContactsController.cs ├── Model │ ├── Appointment.cs │ ├── Contact.cs │ └── Task.cs ├── Properties │ └── AssemblyInfo.cs ├── README.md ├── RequireScopeAttribute.cs ├── Startup.cs ├── TasksController.cs ├── Web.Debug.config ├── Web.Release.config ├── Web.config └── packages.config └── organizer-nodejs ├── .deployment ├── README.md ├── config.json ├── deploy.sh ├── logger.js ├── package.json └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | 30 | [Oo]bj 31 | [Bb]in 32 | *.user 33 | target 34 | 35 | *.suo 36 | 37 | *.cache 38 | 39 | *.bak 40 | 41 | *.mdf 42 | *.ldf 43 | 44 | TestResults/ 45 | packages 46 | *.tss -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Auth0 Customer Collaboration 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /docs/media/giftdeliveries-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-samples/auth0-api-auth-samples/82e5127d0411c3b9e49eb30d297f59ba403834dd/docs/media/giftdeliveries-client.png -------------------------------------------------------------------------------- /docs/media/giftdeliveries-grants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-samples/auth0-api-auth-samples/82e5127d0411c3b9e49eb30d297f59ba403834dd/docs/media/giftdeliveries-grants.png -------------------------------------------------------------------------------- /docs/media/machine-to-machine-samples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-samples/auth0-api-auth-samples/82e5127d0411c3b9e49eb30d297f59ba403834dd/docs/media/machine-to-machine-samples.png -------------------------------------------------------------------------------- /docs/media/worldmappers-rs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-samples/auth0-api-auth-samples/82e5127d0411c3b9e49eb30d297f59ba403834dd/docs/media/worldmappers-rs.png -------------------------------------------------------------------------------- /docs/media/worldmappers-scopes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-samples/auth0-api-auth-samples/82e5127d0411c3b9e49eb30d297f59ba403834dd/docs/media/worldmappers-scopes.png -------------------------------------------------------------------------------- /machine-to-machine/README.md: -------------------------------------------------------------------------------- 1 | # Auth0 API Authorization - Machine to Machine 2 | 3 | These samples show how to setup machine to machine flows using the OAuth2 `client_credentials` flow in Auth0 ([more information](https://auth0.com/docs/api-auth)). 4 | 5 | In this flow you'll have: 6 | 7 | - An Authorization Server (Auth0) 8 | - A Resource Server (your API, in this example the **World Mappers API**) 9 | - One or more Clients (a consumer of the API, in this example **Gift Deliveries**) 10 | 11 | ## World Mappers API 12 | 13 | The team at World Mappers first registered their API [as a Resource Server in Auth0](https://manage.auth0.com/#/apis): 14 | 15 |  16 | 17 | After creating the RS they also specified the scopes (permissions) available in this Resource Server: 18 | 19 |  20 | 21 | A client calling this API could be granted any of these scopes. 22 | 23 | The [worldmappers-api-nodejs](./worldmappers-api-nodejs) sample shows how you would configure your API to be a Resource Server. This is pretty straightforward: you accept tokens issued by the Authorization Server (`iss: https://YOUR_DOMAIN/`) to a Resource Server (`aud: urn:worldmappers`). 24 | 25 | By default these tokens will be signed using RS256 so your API will need some logic to retrieve that public key (from: `https://YOUR_DOMAIN/.well-known/jwks.json`). 26 | 27 | And finally your code needs some logic that inspects the contents of the token and validates that the client has the right scope to call a given endpoint: 28 | 29 | ```js 30 | app.get('/api/location/geocode', requireScope('geocode:location'), function(req, res, next) { 31 | res.json({ 32 | lat: 47.6178819, 33 | lng: -122.194041 34 | }); 35 | }); 36 | ``` 37 | 38 | ## Gift Deliveries Client 39 | 40 | After setting up the Resource Server you can register the client for Gift Deliveries. 41 | 42 |  43 | 44 | By default a Client is not authorized to access any of the Resource Servers. Which is why the next step is to authorize the client for the Resource Server and define which scopes are enabled for this client. 45 | 46 |  47 | 48 | Finally the client can call the `https://YOUR_DOMAIN/oauth/token` endpoint to get an access token which can then be used to call the World Mappers API (RS). 49 | 50 | ```js 51 | var options = { 52 | method: 'POST', 53 | url: 'https://' + env('AUTH0_DOMAIN') + '/oauth/token', 54 | headers: { 55 | 'content-type': 'application/json' 56 | }, 57 | body: { 58 | audience: env('RESOURCE_SERVER'), 59 | grant_type: 'client_credentials', 60 | client_id: env('AUTH0_CLIENT_ID'), 61 | client_secret: env('AUTH0_CLIENT_SECRET') 62 | }, 63 | json: true 64 | }; 65 | 66 | request(options, function(err, res, body) { 67 | if (err || res.statusCode < 200 || res.statusCode >= 300) { 68 | return callback(res && res.body || err); 69 | } 70 | 71 | callback(null, body.access_token); 72 | }); 73 | ``` 74 | 75 | ## Running the Node.js samples 76 | 77 | Before running the Node.js samples make sure you update the `config.json` file in both projects with your own account information. 78 | 79 |  80 | -------------------------------------------------------------------------------- /machine-to-machine/giftdeliveries-nodejs/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "RESOURCE_SERVER": "urn:worldmappers", 3 | "AUTH0_DOMAIN": "YOUR_DOMAIN", 4 | "AUTH0_CLIENT_ID": "YOUR_CLIENT_ID", 5 | "AUTH0_CLIENT_SECRET": "YOUR_CLIENT_SECRET" 6 | } 7 | -------------------------------------------------------------------------------- /machine-to-machine/giftdeliveries-nodejs/index.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | 3 | var env = require('./lib/env'); 4 | var logger = require('./lib/logger'); 5 | 6 | /* 7 | * Helper method to get an access token from the Authorization Server. 8 | */ 9 | var getAccessToken = function(callback) { 10 | if (!env('AUTH0_DOMAIN')) { 11 | callback(new Error('The AUTH0_DOMAIN is required in order to get an access token (verify your configuration).')); 12 | } 13 | 14 | logger.debug('Fetching access token from https://' + env('AUTH0_DOMAIN') + '/oauth/token'); 15 | 16 | var options = { 17 | method: 'POST', 18 | url: 'https://' + env('AUTH0_DOMAIN') + '/oauth/token', 19 | headers: { 20 | 'cache-control': 'no-cache', 21 | 'content-type': 'application/json' 22 | }, 23 | body: { 24 | audience: env('RESOURCE_SERVER'), 25 | grant_type: 'client_credentials', 26 | client_id: env('AUTH0_CLIENT_ID'), 27 | client_secret: env('AUTH0_CLIENT_SECRET') 28 | }, 29 | json: true 30 | }; 31 | 32 | request(options, function(err, res, body) { 33 | if (err || res.statusCode < 200 || res.statusCode >= 300) { 34 | return callback(res && res.body || err); 35 | } 36 | 37 | callback(null, body.access_token); 38 | }); 39 | } 40 | 41 | logger.info('Starting the Gift Deliveries worker.'); 42 | 43 | // Get the access token. 44 | getAccessToken(function(err, accessToken) { 45 | if (err) { 46 | logger.error('Error getting a token:', err.message || err); 47 | return; 48 | } 49 | 50 | logger.info('Getting directions to the Auth0 Office from the World Mappers API'); 51 | 52 | // Call the Worldmappers API with the access token. 53 | var options = { 54 | url: 'http://localhost:7001/api/directions?destination=Auth0%20Office', 55 | headers: { 56 | Authorization: 'Bearer ' + accessToken 57 | } 58 | } 59 | request.get(options, function(err, res, body) { 60 | if (err || res.statusCode < 200 || res.statusCode >= 300) { 61 | logger.error(res && res.body || err); 62 | } else { 63 | logger.info('Directions:', body); 64 | } 65 | }); 66 | }) 67 | -------------------------------------------------------------------------------- /machine-to-machine/giftdeliveries-nodejs/lib/env.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var nconf = require('nconf'); 3 | 4 | nconf.env() 5 | .file({ file: path.join(__dirname, '../config.json') }) 6 | .defaults({ 7 | PORT: 7001 8 | }); 9 | 10 | module.exports = function(key) { 11 | return nconf.get(key); 12 | }; 13 | -------------------------------------------------------------------------------- /machine-to-machine/giftdeliveries-nodejs/lib/logger.js: -------------------------------------------------------------------------------- 1 | var winston = require('winston'); 2 | winston.emitErrs = true; 3 | 4 | var logger = new winston.Logger({ 5 | transports: [ 6 | new winston.transports.Console({ 7 | timestamp: true, 8 | level: 'debug', 9 | handleExceptions: true, 10 | json: false, 11 | colorize: true 12 | }) 13 | ], 14 | exitOnError: false 15 | }); 16 | 17 | module.exports = logger; 18 | module.exports.stream = { 19 | write: function(message, encoding) { 20 | logger.info(message.replace(/\n$/, '')); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /machine-to-machine/giftdeliveries-nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "giftdeliveries-nodejs", 3 | "version": "1.0.0", 4 | "description": "Sample client for the Worldmappers API", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "auth0" 11 | ], 12 | "author": "Auth0", 13 | "license": "MIT", 14 | "dependencies": { 15 | "nconf": "^0.8.4", 16 | "request": "^2.72.0", 17 | "winston": "^2.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /machine-to-machine/worldmappers-api-nodejs/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "AUTH0_DOMAIN": "YOUR_DOMAIN", 3 | "RESOURCE_SERVER": "urn:worldmappers" 4 | } 5 | -------------------------------------------------------------------------------- /machine-to-machine/worldmappers-api-nodejs/lib/env.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var nconf = require('nconf'); 3 | 4 | nconf.env() 5 | .file({ file: path.join(__dirname, '../config.json') }) 6 | .defaults({ 7 | PORT: 7001 8 | }); 9 | 10 | module.exports = function(key) { 11 | return nconf.get(key); 12 | }; 13 | -------------------------------------------------------------------------------- /machine-to-machine/worldmappers-api-nodejs/lib/getPublicKey.js: -------------------------------------------------------------------------------- 1 | var ms = require('ms'); 2 | var request = require('request'); 3 | 4 | var logger = require('./logger'); 5 | 6 | function certToPEM (cert) { 7 | cert = cert.match(/.{1,64}/g).join('\n'); 8 | cert = "-----BEGIN CERTIFICATE-----\n" + cert; 9 | cert = cert + "\n-----END CERTIFICATE-----\n"; 10 | return cert; 11 | } 12 | 13 | function getPublicKeyFromJwks(domain, callback) { 14 | var options = { 15 | url: 'https://' + domain + '/.well-known/jwks.json', 16 | json: true 17 | }; 18 | 19 | logger.debug('Loading public key from: https://' + domain + '/.well-known/jwks.json') 20 | request(options, function(err, res) { 21 | if (err || res.statusCode < 200 || res.statusCode >= 300) { 22 | return callback(res && res.body || err); 23 | } 24 | 25 | var key = res.body.keys.find((key) => key.alg === 'RS256'); 26 | if (!key) { 27 | return callback(new Error('Unable to find public key for: ' + domain)); 28 | } 29 | 30 | return callback(null, certToPEM(key.x5c[0])); 31 | }); 32 | } 33 | 34 | module.exports = function(domain) { 35 | if (!domain) { 36 | throw new Error('The domain is required in order to load the public key.'); 37 | } 38 | 39 | var jwksError = null; 40 | var jwksPublicKey = null; 41 | 42 | // Fetch the public key every 10 hours to support key rotation. 43 | const getPublicKey = function() { 44 | getPublicKeyFromJwks(domain, function(err, publicKey) { 45 | if (err) { 46 | jwksError = err; 47 | logger.error('Error loading public key for: ' + domain, err); 48 | } else { 49 | jwksPublicKey = publicKey; 50 | logger.debug('Loaded public key for: ' + domain); 51 | } 52 | }); 53 | }; 54 | getPublicKey(); 55 | setInterval(getPublicKey, ms('10h')); 56 | 57 | // Function to return the public key. 58 | return function(req, header, payload, cb) { 59 | if (!jwksPublicKey) { 60 | return cb(err || new Error('Public key not available.')); 61 | } 62 | 63 | return cb(null, jwksPublicKey); 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /machine-to-machine/worldmappers-api-nodejs/lib/logger.js: -------------------------------------------------------------------------------- 1 | var winston = require('winston'); 2 | winston.emitErrs = true; 3 | 4 | var logger = new winston.Logger({ 5 | transports: [ 6 | new winston.transports.Console({ 7 | timestamp: true, 8 | level: 'debug', 9 | handleExceptions: true, 10 | json: false, 11 | colorize: true 12 | }) 13 | ], 14 | exitOnError: false 15 | }); 16 | 17 | module.exports = logger; 18 | module.exports.stream = { 19 | write: function(message, encoding) { 20 | logger.info(message.replace(/\n$/, '')); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /machine-to-machine/worldmappers-api-nodejs/lib/middleware/requireScope.js: -------------------------------------------------------------------------------- 1 | module.exports = function(expected_scope){ 2 | return function (req, res, next){ 3 | if (!req.user || !req.user.scope || req.user.scope.split(' ').indexOf(expected_scope) < 0) { 4 | return next(new Error('Cannot perform action. Missing scope ' + expected_scope)); 5 | } 6 | next(); 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /machine-to-machine/worldmappers-api-nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "worldmappers-nodejs", 3 | "version": "1.0.0", 4 | "description": "Sample Resource Server", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "auth0" 11 | ], 12 | "author": "Auth0", 13 | "license": "MIT", 14 | "dependencies": { 15 | "cors": "^2.7.1", 16 | "express": "^4.13.4", 17 | "express-jwt": "^3.4.0", 18 | "morgan": "^1.7.0", 19 | "ms": "^0.7.1", 20 | "nconf": "^0.8.4", 21 | "request": "^2.72.0", 22 | "winston": "^2.2.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /machine-to-machine/worldmappers-api-nodejs/server.js: -------------------------------------------------------------------------------- 1 | const jwt = require('express-jwt'); 2 | const http = require('http'); 3 | const morgan = require('morgan'); 4 | 5 | const env = require('./lib/env'); 6 | const logger = require('./lib/logger'); 7 | const getPublicKey = require('./lib/getPublicKey'); 8 | const requireScope = require('./lib/middleware/requireScope'); 9 | 10 | /* 11 | * Initialize express. 12 | */ 13 | const express = require('express'); 14 | const app = express(); 15 | app.use(morgan(':method :url :status :response-time ms - :res[content-length]', { 16 | stream: logger.stream 17 | })); 18 | 19 | /* 20 | * Middleware that will validate the incoming access token. 21 | */ 22 | const jwtCheck = jwt({ 23 | secret: getPublicKey(env('AUTH0_DOMAIN')), 24 | audience: env('RESOURCE_SERVER'), 25 | algorithms: [ 'RS256' ], 26 | issuer: `https://${env('AUTH0_DOMAIN')}/` 27 | }); 28 | 29 | /* 30 | * API endpoints. 31 | */ 32 | app.use('/api', jwtCheck, function(req, res, next) { 33 | if (req.user) { 34 | logger.debug('Current user: ' + req.user.sub + ' (scope=' + (req.user.scope || 'N/A') + ')'); 35 | } 36 | next(); 37 | }); 38 | app.get('/api/location/geocode', requireScope('geocode:location'), function(req, res, next) { 39 | res.json({ 40 | lat: 47.6178819, 41 | lng: -122.194041 42 | }); 43 | }); 44 | app.get('/api/location/reverse-geocode', requireScope('reverse-geocode:location'), function(req, res, next) { 45 | res.json({ 46 | street: '10900 NE 8th Street', 47 | city: 'Bellevue', 48 | state: 'Washington' 49 | }); 50 | }); 51 | app.get('/api/directions', requireScope('directions'), function(req, res, next) { 52 | res.json([ 53 | { step: 1, action: 'Turn left' }, 54 | { step: 2, action: 'Turn right' }, 55 | { step: 3, action: 'Finish' } 56 | ]); 57 | }); 58 | 59 | /* 60 | * Error handler 61 | */ 62 | app.use(function(err, req, res, next) { 63 | if (err) { 64 | logger.error('Unauthorized:', err.message); 65 | return res.status(401).send({ message: err.message }); 66 | } 67 | 68 | next(err, req, res); 69 | }); 70 | 71 | /* 72 | * Start server. 73 | */ 74 | http.createServer(app).listen(env('PORT'), function() { 75 | logger.info('Worldmappers API (Resource Server) listening on: http://localhost:' + env('PORT')); 76 | }); 77 | -------------------------------------------------------------------------------- /user-consent/README.md: -------------------------------------------------------------------------------- 1 | # Auth0 API Authorization 2 | 3 | ## User Consent 4 | 5 | These samples show the use case for **Organizer** a platform allowing you to manage your contacts, appointments and tasks. They are now opening up their API to third party applications like **CalendarApp**, a calendar application allowing you to manage your calendars from different providers (Gmail, Outlook, ...) including **Organizer** 6 | -------------------------------------------------------------------------------- /user-consent/calendarapp-authz-code-nodejs/.deployment: -------------------------------------------------------------------------------- 1 | [config] 2 | command = bash deploy.sh 3 | -------------------------------------------------------------------------------- /user-consent/calendarapp-authz-code-nodejs/README.md: -------------------------------------------------------------------------------- 1 | # Client: CalendarApp (Node.js - Authorization Code Grant) 2 | 3 | Demo: https://api-authz-calendar-authorization-code.azurewebsites.net/ 4 | 5 | ## Configuration 6 | 7 | Before running this sample you must first make sure your Resource Server is running and you also need to define the Client in Auth0: 8 | 9 | ``` 10 | curl -XPOST 'https://AUTH0_DOMAIN/api/v2/clients' -H "Authorization: Bearer AUTH0_API2_TOKEN" -H "Content-Type: application/json" -d '{ 11 | "name": "Calendar App - Authorization Code", 12 | "resource_servers": [ 13 | { "identifier": "urn:organizer-api", "scopes": [ "appointments", "contacts" ] } 14 | ], 15 | "callbacks": [ "http://localhost:7003/auth/organizer/callback" ] 16 | }' 17 | ``` 18 | 19 | Capture the result from this call, especially the `client_id` and `client_secret` (you will need to enter this in the `config.json` file). 20 | 21 | More information is available [here](https://auth0.com/docs/api-authn-authz#tutorials). 22 | 23 | ## Running the sample 24 | 25 | 1. Update the `config.json` file with your own settings 26 | 2. Run `npm install` 27 | 3. Run `node server` 28 | 4. The web application will start http://localhost:7002 29 | 5. Sign in and try to get your data from the Resource Server 30 | -------------------------------------------------------------------------------- /user-consent/calendarapp-authz-code-nodejs/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "AUTH0_DOMAIN": "{AUTH0_DOMAIN}", 3 | "AUTH0_CLIENT_ID": "{AUTH0_CLIENT_ID}", 4 | "AUTH0_CLIENT_SECRET": "{AUTH0_CLIENT_SECRET}", 5 | "ORGANIZER_BASE_URL": "http://localhost:7001" 6 | } 7 | -------------------------------------------------------------------------------- /user-consent/calendarapp-authz-code-nodejs/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ---------------------- 4 | # KUDU Deployment Script 5 | # Version: 0.2.2 6 | # ---------------------- 7 | 8 | # Helpers 9 | # ------- 10 | 11 | exitWithMessageOnError () { 12 | if [ ! $? -eq 0 ]; then 13 | echo "An error has occurred during web site deployment." 14 | echo $1 15 | exit 1 16 | fi 17 | } 18 | 19 | # Prerequisites 20 | # ------------- 21 | 22 | # Verify node.js installed 23 | hash node 2>/dev/null 24 | exitWithMessageOnError "Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment." 25 | 26 | # Setup 27 | # ----- 28 | 29 | SCRIPT_DIR="${BASH_SOURCE[0]%\\*}" 30 | SCRIPT_DIR="${SCRIPT_DIR%/*}" 31 | ARTIFACTS=$SCRIPT_DIR/../artifacts 32 | KUDU_SYNC_CMD=${KUDU_SYNC_CMD//\"} 33 | 34 | if [[ ! -n "$DEPLOYMENT_SOURCE" ]]; then 35 | DEPLOYMENT_SOURCE=$SCRIPT_DIR 36 | fi 37 | 38 | if [[ ! -n "$NEXT_MANIFEST_PATH" ]]; then 39 | NEXT_MANIFEST_PATH=$ARTIFACTS/manifest 40 | 41 | if [[ ! -n "$PREVIOUS_MANIFEST_PATH" ]]; then 42 | PREVIOUS_MANIFEST_PATH=$NEXT_MANIFEST_PATH 43 | fi 44 | fi 45 | 46 | if [[ ! -n "$DEPLOYMENT_TARGET" ]]; then 47 | DEPLOYMENT_TARGET=$ARTIFACTS/wwwroot 48 | else 49 | KUDU_SERVICE=true 50 | fi 51 | 52 | if [[ ! -n "$KUDU_SYNC_CMD" ]]; then 53 | # Install kudu sync 54 | echo Installing Kudu Sync 55 | npm install kudusync -g --silent 56 | exitWithMessageOnError "npm failed" 57 | 58 | if [[ ! -n "$KUDU_SERVICE" ]]; then 59 | # In case we are running locally this is the correct location of kuduSync 60 | KUDU_SYNC_CMD=kuduSync 61 | else 62 | # In case we are running on kudu service this is the correct location of kuduSync 63 | KUDU_SYNC_CMD=$APPDATA/npm/node_modules/kuduSync/bin/kuduSync 64 | fi 65 | fi 66 | 67 | # Node Helpers 68 | # ------------ 69 | 70 | selectNodeVersion () { 71 | if [[ -n "$KUDU_SELECT_NODE_VERSION_CMD" ]]; then 72 | SELECT_NODE_VERSION="$KUDU_SELECT_NODE_VERSION_CMD \"$DEPLOYMENT_SOURCE\" \"$DEPLOYMENT_TARGET\" \"$DEPLOYMENT_TEMP\"" 73 | eval $SELECT_NODE_VERSION 74 | exitWithMessageOnError "select node version failed" 75 | 76 | if [[ -e "$DEPLOYMENT_TEMP/__nodeVersion.tmp" ]]; then 77 | NODE_EXE=`cat "$DEPLOYMENT_TEMP/__nodeVersion.tmp"` 78 | exitWithMessageOnError "getting node version failed" 79 | fi 80 | 81 | if [[ -e "$DEPLOYMENT_TEMP/.tmp" ]]; then 82 | NPM_JS_PATH=`cat "$DEPLOYMENT_TEMP/__npmVersion.tmp"` 83 | exitWithMessageOnError "getting npm version failed" 84 | fi 85 | 86 | if [[ ! -n "$NODE_EXE" ]]; then 87 | NODE_EXE=node 88 | fi 89 | 90 | NPM_CMD="\"$NODE_EXE\" \"$NPM_JS_PATH\"" 91 | else 92 | NPM_CMD=npm 93 | NODE_EXE=node 94 | fi 95 | } 96 | 97 | ################################################################################################################################## 98 | # Deployment 99 | # ---------- 100 | 101 | echo Handling node.js deployment. 102 | 103 | # 1. KuduSync 104 | if [[ "$IN_PLACE_DEPLOYMENT" -ne "1" ]]; then 105 | "$KUDU_SYNC_CMD" -v 50 -f "$DEPLOYMENT_SOURCE" -t "$DEPLOYMENT_TARGET" -n "$NEXT_MANIFEST_PATH" -p "$PREVIOUS_MANIFEST_PATH" -i ".git;.hg;.deployment;deploy.sh" 106 | exitWithMessageOnError "Kudu Sync failed" 107 | fi 108 | 109 | # 2. Select node version 110 | selectNodeVersion 111 | 112 | # 3. Install npm packages 113 | if [ -e "$DEPLOYMENT_TARGET/package.json" ]; then 114 | cd "$DEPLOYMENT_TARGET" 115 | eval $NPM_CMD install --production 116 | exitWithMessageOnError "npm failed" 117 | cd - > /dev/null 118 | fi 119 | ################################################################################################################################## 120 | 121 | # Post deployment stub 122 | if [[ -n "$POST_DEPLOYMENT_ACTION" ]]; then 123 | POST_DEPLOYMENT_ACTION=${POST_DEPLOYMENT_ACTION//\"} 124 | cd "${POST_DEPLOYMENT_ACTION_DIR%\\*}" 125 | "$POST_DEPLOYMENT_ACTION" 126 | exitWithMessageOnError "post deployment action failed" 127 | fi 128 | 129 | echo "Finished successfully." 130 | -------------------------------------------------------------------------------- /user-consent/calendarapp-authz-code-nodejs/logger.js: -------------------------------------------------------------------------------- 1 | var winston = require('winston'); 2 | winston.emitErrs = true; 3 | 4 | var logger = new winston.Logger({ 5 | transports: [ 6 | new winston.transports.Console({ 7 | timestamp: true, 8 | level: 'debug', 9 | handleExceptions: true, 10 | json: false, 11 | colorize: true 12 | }) 13 | ], 14 | exitOnError: false 15 | }); 16 | 17 | module.exports = logger; 18 | module.exports.stream = { 19 | write: function(message, encoding){ 20 | logger.info(message.replace(/\n$/, '')); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /user-consent/calendarapp-authz-code-nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "calendarapp-authz-code-nodejs", 3 | "version": "1.0.0", 4 | "engines": { "node": "0.12.0" }, 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cookie-parser": "^1.4.0", 14 | "express": "^4.13.3", 15 | "express-hbs": "^0.8.4", 16 | "express-session": "^1.12.1", 17 | "jsonwebtoken": "^5.4.1", 18 | "morgan": "^1.6.1", 19 | "nconf": "^0.8.2", 20 | "passport": "^0.3.2", 21 | "passport-oauth2": "^1.1.2", 22 | "request": "^2.65.0", 23 | "winston": "^2.1.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /user-consent/calendarapp-authz-code-nodejs/server.js: -------------------------------------------------------------------------------- 1 | var jwt = require('jsonwebtoken'); 2 | var http = require('http'); 3 | var path = require('path'); 4 | var morgan = require('morgan'); 5 | var logger = require('./logger'); 6 | var request = require('request'); 7 | 8 | var express = require('express'); 9 | var session = require('express-session'); 10 | var cookieParser = require('cookie-parser'); 11 | var passport = require('passport'); 12 | var oauth2 = require('passport-oauth2'); 13 | var hbs = require('express-hbs'); 14 | 15 | var app = express(); 16 | app.use(cookieParser()); 17 | app.use(session({ secret: 'shhhhhhhhh' })); 18 | app.use(morgan(':method :url :status :response-time ms - :res[content-length]', { 19 | stream: logger.stream 20 | })); 21 | 22 | app.engine('hbs', hbs.express4({ 23 | defaultLayout: path.join(__dirname, 'views/layout/default.hbs') 24 | })); 25 | app.set('view engine', 'hbs'); 26 | app.set('views', path.join(__dirname, 'views')); 27 | 28 | var nconf = require('nconf'); 29 | nconf.env() 30 | .file({ file: './config.json' }) 31 | .defaults({ 32 | PORT: 7003, 33 | CALLBACK_URL: "http://localhost:7003/auth/organizer/callback" 34 | }); 35 | 36 | /* 37 | * Configure passport. 38 | */ 39 | passport.serializeUser(function(user, done) { 40 | console.log('serialize user'); 41 | done(null, user); 42 | }); 43 | passport.deserializeUser(function(user, done) { 44 | console.log('deserialize user'); 45 | done(null, user); 46 | }); 47 | passport.use(new oauth2.Strategy({ 48 | authorizationURL: 'https://' + nconf.get('AUTH0_DOMAIN') + '/i/oauth2/authorize', 49 | tokenURL: 'https://' + nconf.get('AUTH0_DOMAIN') + '/oauth/token', 50 | clientID: nconf.get('AUTH0_CLIENT_ID'), 51 | clientSecret: nconf.get('AUTH0_CLIENT_SECRET'), 52 | callbackURL: nconf.get('CALLBACK_URL'), 53 | skipUserProfile: true 54 | }, function(accessToken, refreshToken, profile, done) { 55 | var payload = jwt.decode(accessToken); 56 | 57 | logger.info('Token received for:', payload.sub); 58 | 59 | done(null, { 60 | id: payload.sub, 61 | access_token: accessToken 62 | }); 63 | })); 64 | 65 | /* 66 | * Initialize passport. 67 | */ 68 | app.use(passport.initialize()); 69 | app.use(passport.session()); 70 | 71 | /* 72 | * Middleware to require authentication. 73 | */ 74 | var requiresLogin = function(req, res, next) { 75 | console.log(req.user); 76 | if (!req.isAuthenticated()) { 77 | return res.redirect('/'); 78 | } 79 | next(); 80 | }; 81 | 82 | /* 83 | * Pages 84 | */ 85 | app.get('/', function(req, res, next) { 86 | res.render('index'); 87 | }); 88 | 89 | app.get('/account', requiresLogin, function(req, res, next) { 90 | res.render('account', { 91 | user: req.user, 92 | user_json: JSON.stringify(req.user, null, 2), 93 | access_token: req.user.access_token, 94 | access_token_payload: JSON.stringify(jwt.decode(req.user.access_token), null, 2) 95 | }); 96 | }); 97 | 98 | app.get('/appointments', requiresLogin, function(req, res, next) { 99 | request({ 100 | url: nconf.get('ORGANIZER_BASE_URL') + '/api/appointments', 101 | json: true, 102 | headers: { 103 | 'Authorization': 'Bearer ' + req.user.access_token 104 | } 105 | }, function(error, response, body) { 106 | if (error) { 107 | logger.error(error); 108 | return res.status(500); 109 | } else { 110 | res.render('appointments', { 111 | user: req.user, 112 | appointments: JSON.stringify(body, null, 2) 113 | }); 114 | } 115 | }); 116 | }); 117 | 118 | app.get('/contacts', requiresLogin, function(req, res, next) { 119 | request({ 120 | url: nconf.get('ORGANIZER_BASE_URL') + '/api/contacts', 121 | json: true, 122 | headers: { 123 | 'Authorization': 'Bearer ' + req.user.access_token 124 | } 125 | }, function(error, response, body) { 126 | if (error) { 127 | logger.error(error); 128 | return res.status(500); 129 | } else { 130 | res.render('contacts', { 131 | user: req.user, 132 | contacts: JSON.stringify(body, null, 2) 133 | }); 134 | } 135 | }); 136 | }); 137 | 138 | app.get('/tasks', requiresLogin, function(req, res, next) { 139 | request({ 140 | url: nconf.get('ORGANIZER_BASE_URL') + '/api/tasks', 141 | json: true, 142 | headers: { 143 | 'Authorization': 'Bearer ' + req.user.access_token 144 | } 145 | }, function(error, response, body) { 146 | if (error) { 147 | logger.error(error); 148 | return res.status(500); 149 | } else { 150 | res.render('tasks', { 151 | user: req.user, 152 | tasks: JSON.stringify(body, null, 2) 153 | }); 154 | } 155 | }); 156 | }); 157 | 158 | /* 159 | * Login with 'Organizer' (the Resource Server) 160 | */ 161 | app.get('/auth/organizer', 162 | passport.authenticate('oauth2', { scope: 'appointments contacts openid email' })); 163 | 164 | /* 165 | * Handle callback from the Authorization Server. 166 | */ 167 | app.get('/auth/organizer/callback', 168 | passport.authenticate('oauth2', { failureRedirect: '/' }), 169 | function(req, res) { 170 | logger.debug('Login:', req.user.access_token); 171 | res.redirect('/account'); 172 | }); 173 | /* 174 | * Start server. 175 | */ 176 | http.createServer(app).listen(nconf.get('PORT'), function() { 177 | logger.info('CalendarApp listening on: http://localhost:' + nconf.get('PORT')); 178 | logger.info(' > Mode: client - authorization code grant'); 179 | }); 180 | -------------------------------------------------------------------------------- /user-consent/calendarapp-authz-code-nodejs/views/account.hbs: -------------------------------------------------------------------------------- 1 |
Here is the current user object:
11 |{{ user_json }}12 | 13 |
Here is the access_token issued by the Authorization Server.
15 |{{ access_token }}16 |
And these are the contents of the access_token.
17 |{{ access_token_payload }}18 |
These are the appointments for {{ user.id }} we received from Oranizer (by calling the API on your behalf)
11 |{{ appointments }}12 |
These are the contacts for {{ user.id }} we received from Oranizer (by calling the API on your behalf)
11 |{{ contacts }}12 |
Request authorization from "Organizer" to access your appointments.
6 |This is the response we received from Oranizer when trying to get the tasks for your account. Remember that you did not give consent for tasks, so CalenderApp should not be allowed to access this data.
11 |{{ tasks }}12 |
Request authorization from "Organizer" to access your appointments.
17 |