├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── auth.js ├── db.js ├── oauth.js ├── package.json ├── registration.js ├── utils.js └── views ├── clientRegistration.jade ├── decision.jade ├── layout.jade ├── login.jade └── userRegistration.jade /.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 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | oauth2orize_implicit_example 2 | ============================ 3 | 4 | This is an example of the oAuth implicit flow using oauth2orize, express 4 and mongoDB. 5 | 6 | ##### Installation 7 | 8 | ``` 9 | git clone https://github.com/reneweb/oauth2orize_implicit_example.git 10 | npm install 11 | node app.js 12 | ``` 13 | Note: You may need to change the database configuration in the db.js file, if mongoDB doesn't run using the default port or is not running on localhost. 14 | 15 | ##### Usage (with cURL) 16 | 17 | ###### 0 - Register a client 18 | 19 | Navigate to /client/registration. Register a new client. 20 | 21 | ###### 1 - Register a user 22 | 23 | Navigate to /registration. Register a new user. 24 | 25 | ###### 2 - Get an access token 26 | 27 | Navigate to /oauth/authorization?clientId=<clientId>&redirectUri=<redirectUri>&responseType=token. Login with username and password. Then allow the client to access your account. 28 | If everything works the access code is returned in the fragment identifier of the URL. 29 | 30 | 31 | ###### 3 - Access a restricted resource using the access token 32 | 33 | ``` 34 | curl -X GET :/restricted -v -H "Authorization: Bearer " 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | //Module dependencies 2 | var express = require('express') 3 | , http = require('http') 4 | , passport = require('passport') 5 | , util = require('util') 6 | , session = require('express-session') 7 | , cookieParser = require('cookie-parser') 8 | , bodyParser = require('body-parser') 9 | , expressValidator = require('express-validator') 10 | , auth = require("./auth") 11 | , oauth = require("./oauth") 12 | , registration = require("./registration") 13 | 14 | // Express configuration 15 | var app = express() 16 | app.set('views', __dirname + '/views') 17 | app.set('view engine', 'jade') 18 | app.use(bodyParser()) 19 | app.use(expressValidator()) 20 | app.use(cookieParser()) 21 | app.use(session({ secret: 'keyboard cat1'})) 22 | 23 | app.use(passport.initialize()) 24 | app.use(passport.session()) 25 | 26 | app.get('/client/registration', function(req, res) { res.render('clientRegistration') }) 27 | app.post('/client/registration', registration.registerClient) 28 | 29 | app.get('/registration', function(req, res) { res.render('userRegistration') }) 30 | app.post('/registration', registration.registerUser) 31 | 32 | app.get('/oauth/authorization', function(req, res) { res.render('login', {clientId : req.query.clientId, redirectUri: req.query.redirectUri, responseType: req.query.responseType}) }) 33 | app.post('/oauth/authorization', passport.authenticate('local', { failureRedirect: '/oauth/authorization' }), function(req, res) { 34 | //It is not essential for the flow to redirect here, it would also be possible to call this directly 35 | res.redirect('/authorization?response_type=' + req.body.responseType + '&client_id=' + req.body.clientId + '&redirect_uri=' + req.body.redirectUri) 36 | }) 37 | 38 | app.get('/authorization', oauth.authorization) 39 | app.post('/decision', oauth.decision) 40 | 41 | app.get('/restricted', passport.authenticate('accessToken', { session: false }), function (req, res) { 42 | res.send("Yay, you successfully accessed the restricted resource!") 43 | }) 44 | 45 | //Start 46 | http.createServer(app).listen(process.env.PORT || 3000, process.env.IP || "0.0.0.0") 47 | -------------------------------------------------------------------------------- /auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var passport = require('passport') 5 | , LocalStrategy = require('passport-local').Strategy 6 | , BasicStrategy = require('passport-http').BasicStrategy 7 | , ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy 8 | , BearerStrategy = require('passport-http-bearer').Strategy 9 | , db = require('./db').db() 10 | , bcrypt = require('bcrypt') 11 | , crypto = require('crypto') 12 | 13 | 14 | /** 15 | * LocalStrategy 16 | */ 17 | passport.use(new LocalStrategy( 18 | function(username, password, done) { 19 | db.collection('users').findOne({username: username}, function(err, user) { 20 | if (err) { return done(err); } 21 | if (!user) { return done(null, false); } 22 | bcrypt.compare(password, user.password, function (err, res) { 23 | if (!res) return done(null, false) 24 | return done(null, user) 25 | }) 26 | }) 27 | } 28 | )) 29 | 30 | passport.serializeUser(function(user, done) { 31 | done(null, user.username); 32 | }) 33 | 34 | passport.deserializeUser(function(id, done) { 35 | db.collection('users').findOne({username: id}, function (err, user) { 36 | done(err, user); 37 | }) 38 | }) 39 | 40 | 41 | /** 42 | * These strategies are used to authenticate registered OAuth clients. 43 | * The authentication data may be delivered using the basic authentication scheme (recommended) 44 | * or the client strategy, which means that the authentication data is in the body of the request. 45 | */ 46 | passport.use("clientBasic", new BasicStrategy( 47 | function (clientId, clientSecret, done) { 48 | db.collection('clients').findOne({clientId: clientId}, function (err, client) { 49 | if (err) return done(err) 50 | if (!client) return done(null, false) 51 | 52 | if (client.clientSecret == clientSecret) return done(null, client) 53 | else return done(null, false) 54 | }); 55 | } 56 | )); 57 | 58 | passport.use("clientPassword", new ClientPasswordStrategy( 59 | function (clientId, clientSecret, done) { 60 | db.collection('clients').findOne({clientId: clientId}, function (err, client) { 61 | if (err) return done(err) 62 | if (!client) return done(null, false) 63 | 64 | if (client.clientSecret == clientSecret) return done(null, client) 65 | else return done(null, false) 66 | }); 67 | } 68 | )); 69 | 70 | /** 71 | * This strategy is used to authenticate users based on an access token (aka a 72 | * bearer token). 73 | */ 74 | passport.use("accessToken", new BearerStrategy( 75 | function (accessToken, done) { 76 | var accessTokenHash = crypto.createHash('sha1').update(accessToken).digest('hex') 77 | db.collection('accessTokens').findOne({token: accessTokenHash}, function (err, token) { 78 | if (err) return done(err) 79 | if (!token) return done(null, false) 80 | if (new Date() > token.expirationDate) { 81 | db.collection('accessTokens').remove({token: accessTokenHash}, function (err) { done(err) }) 82 | } else { 83 | db.collection('users').findOne({username: token.userId}, function (err, user) { 84 | if (err) return done(err) 85 | if (!user) return done(null, false) 86 | // no use of scopes for no 87 | var info = { scope: '*' } 88 | done(null, user, info); 89 | }) 90 | } 91 | }) 92 | } 93 | )) 94 | -------------------------------------------------------------------------------- /db.js: -------------------------------------------------------------------------------- 1 | var mongojs = require('mongojs') 2 | 3 | var db = mongojs('oauth2orize_implicit_example') 4 | 5 | exports.db = function() { 6 | return db 7 | } 8 | -------------------------------------------------------------------------------- /oauth.js: -------------------------------------------------------------------------------- 1 | var oauth2orize = require('oauth2orize') 2 | , passport = require('passport') 3 | , db = require('./db').db() 4 | , crypto = require('crypto') 5 | , utils = require("./utils") 6 | , bcrypt = require('bcrypt') 7 | 8 | // create OAuth 2.0 server 9 | var server = oauth2orize.createServer(); 10 | 11 | //(De-)Serialization for clients 12 | server.serializeClient(function(client, done) { 13 | return done(null, client.clientId) 14 | }) 15 | 16 | server.deserializeClient(function(id, done) { 17 | db.collection('clients').find({clientId: id}, function(err, client) { 18 | if (err) return done(err) 19 | //mongojs db.find returns and array - we need just the matching client doc so get the 20 | return done(null, client[0]) 21 | }) 22 | }) 23 | 24 | //Implicit grant 25 | server.grant(oauth2orize.grant.token(function (client, user, ares, done) { 26 | var token = utils.uid(256) 27 | var tokenHash = crypto.createHash('sha1').update(token).digest('hex') 28 | var expirationDate = new Date(new Date().getTime() + (3600 * 1000)) 29 | //typo - client-clienId shoudl be client.clientId 30 | db.collection('accessTokens').save({token: tokenHash, expirationDate: expirationDate, userId: user.username, clientId: client.clientId}, function(err) { 31 | if (err) return done(err) 32 | return done(null, token, {expires_in: expirationDate.toISOString()}) 33 | }) 34 | })) 35 | 36 | // user authorization endpoint 37 | exports.authorization = [ 38 | function(req, res, next) { 39 | if (req.user) next() 40 | else res.redirect('/oauth/authorization') 41 | }, 42 | server.authorization(function(clientId, redirectURI, done) { 43 | db.collection('clients').findOne({clientId: clientId}, function(err, client) { 44 | if (err) return done(err) 45 | // WARNING: For security purposes, it is highly advisable to check that 46 | // redirectURI provided by the client matches one registered with 47 | // the server. For simplicity, this example does not. You have 48 | // been warned. 49 | return done(null, client, redirectURI) 50 | }) 51 | }), 52 | function(req, res) { 53 | res.render('decision', { transactionID: req.oauth2.transactionID, user: req.user, client: req.oauth2.client }); 54 | } 55 | ] 56 | 57 | // user decision endpoint 58 | 59 | exports.decision = [ 60 | function(req, res, next) { 61 | if (req.user) next() 62 | else res.redirect('/oauth/authorization') 63 | }, 64 | server.decision() 65 | ] 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oauth2orize_implicit_example", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "start": "node ./app" 6 | }, 7 | "dependencies": { 8 | "express": "4.x.x", 9 | "express-validator": "2.x.x", 10 | "cookie-parser": "1.x.x", 11 | "express-session": "1.x.x", 12 | "jade": "1.x.x", 13 | "body-parser": "1.x.x", 14 | "mongojs": "0.x.x", 15 | "oauth2orize": "1.x.x", 16 | "passport": "0.2.x", 17 | "passport-local": "1.x.x", 18 | "passport-http-bearer": "1.x.x", 19 | "passport-http": "0.2.x", 20 | "passport-oauth2-client-password": "0.1.x", 21 | "bcrypt": "0.7.x" 22 | } 23 | } -------------------------------------------------------------------------------- /registration.js: -------------------------------------------------------------------------------- 1 | var db = require('./db').db() 2 | , utils = require("./utils") 3 | , bcrypt = require('bcrypt') 4 | 5 | exports.registerUser = function(req, res) { 6 | req.checkBody('username', 'No valid username is given').notEmpty().len(3, 40) 7 | req.checkBody('password', 'No valid password is given').notEmpty().len(6, 50) 8 | 9 | var errors = req.validationErrors() 10 | if (errors) { 11 | res.send(errors, 400) 12 | } else { 13 | var username = req.body['username'] 14 | var password = req.body['password'] 15 | 16 | db.collection('users').findOne({username: username}, function (err, user) { 17 | if(user) { 18 | res.send("Username is already taken", 422) 19 | } else { 20 | bcrypt.hash(password, 11, function (err, hash) { 21 | db.collection('users').save({username: username, password: hash}, function (err) { 22 | res.send({username: username}, 201) 23 | }) 24 | }) 25 | } 26 | }) 27 | } 28 | } 29 | 30 | exports.registerClient = function(req, res) { 31 | req.checkBody('name', 'No valid name is given').notEmpty().len(3, 40) 32 | 33 | var errors = req.validationErrors() 34 | if (errors) { 35 | res.send(errors, 400) 36 | } else { 37 | var name = req.body['name'] 38 | var clientId = utils.uid(8) 39 | var clientSecret = utils.uid(20) 40 | 41 | db.collection('clients').findOne({name: name}, function (err, client) { 42 | if(client) { 43 | res.send("Name is already taken", 422) 44 | } else { 45 | db.collection('clients').save({name: name, clientId: clientId, clientSecret: clientSecret}, function (err) { 46 | res.send({name: name, clientId: clientId, clientSecret: clientSecret}, 201) 47 | }) 48 | } 49 | }) 50 | } 51 | } -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Return a unique identifier with the given `len`. 3 | * 4 | * utils.uid(10); 5 | * // => "FDaS435D2z" 6 | * 7 | * @param {Number} len 8 | * @return {String} 9 | * @api private 10 | */ 11 | exports.uid = function(len) { 12 | var buf = [] 13 | , chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' 14 | , charlen = chars.length; 15 | 16 | for (var i = 0; i < len; ++i) { 17 | buf.push(chars[getRandomInt(0, charlen - 1)]); 18 | } 19 | 20 | return buf.join(''); 21 | }; 22 | 23 | /** 24 | * Return a random int, used by `utils.uid()` 25 | * 26 | * @param {Number} min 27 | * @param {Number} max 28 | * @return {Number} 29 | * @api private 30 | */ 31 | 32 | function getRandomInt(min, max) { 33 | return Math.floor(Math.random() * (max - min + 1)) + min; 34 | } -------------------------------------------------------------------------------- /views/clientRegistration.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | block content 3 | div 4 | form(action='/client/registration',method='post') 5 | p 6 | label(for='name') Name: 7 | input(id='name',type='text',value='', name='name') 8 | p 9 | input(type='submit',value='Sign up') -------------------------------------------------------------------------------- /views/decision.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | block content 3 | p Hi #{user.username} 4 | p 5 | b #{client.name} 6 | | is requesting access to your account. 7 | p Do you approve? 8 | form(action='/decision', method='post') 9 | p 10 | input(name='transaction_id', type='hidden', value='#{transactionID}') 11 | p 12 | input(type='submit',value='Allow', id='allow') 13 | input(type='submit',value='Deny', name='cancel', id='deny') -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | body 4 | block content -------------------------------------------------------------------------------- /views/login.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | block content 3 | div 4 | form(action='/oauth/authorization',method='post') 5 | p 6 | label(for='username') Username: 7 | input(id='username', type='text',value='', name='username') 8 | p 9 | label(for='password') Password: 10 | input(id='password', type='password',value='', name='password') 11 | p 12 | input(id='clientId', type='hidden',value='#{clientId}', name='clientId') 13 | input(id='redirectUri', type='hidden',value='#{redirectUri}', name='redirectUri') 14 | input(id='responseType', type='hidden',value='#{responseType}', name='responseType') 15 | input(type='submit',value='Login') 16 | -------------------------------------------------------------------------------- /views/userRegistration.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | block content 3 | div 4 | form(action='/registration',method='post') 5 | p 6 | label(for='username') Username: 7 | input(id='username', type='text',value='', name='username') 8 | p 9 | label(for='password') Password: 10 | input(id='password', type='password',value='', name='password') 11 | p 12 | input(type='submit',value='Sign up') --------------------------------------------------------------------------------