├── .gitignore ├── index.js ├── model.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | db 2 | node_modules 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyParser = require('body-parser'); 3 | var oauthserver = require('oauth2-server'); 4 | var mongoose = require('mongoose'); 5 | var model = require('./model'); 6 | 7 | var mongoUrl = process.env.MONGO_URL || 'mongodb://localhost/test'; 8 | 9 | mongoose.connect(mongoUrl, function (err, res) { 10 | if (err) { 11 | console.log('ERROR connecting to: ' + mongoUrl + '. ' + err); 12 | } else { 13 | console.log('Succeeded connecting to: ' + mongoUrl); 14 | } 15 | }); 16 | 17 | var app = express(); 18 | 19 | app.use(bodyParser.urlencoded({ extended: true })); 20 | 21 | app.use(bodyParser.json()); 22 | 23 | app.oauth = oauthserver({ 24 | model: model, 25 | grants: ['auth_code', 'password', 'refresh_token'], 26 | debug: true, 27 | accessTokenLifetime: model.accessTokenLifetime 28 | }); 29 | 30 | // Handle token grant requests 31 | app.all('/oauth/token', app.oauth.grant()); 32 | 33 | app.get('/secret', app.oauth.authorise(), function (req, res) { 34 | // Will require a valid access_token 35 | res.send('Secret area'); 36 | }); 37 | 38 | app.get('/public', function (req, res) { 39 | // Does not require an access_token 40 | res.send('Public area'); 41 | }); 42 | 43 | // Error handling 44 | app.use(app.oauth.errorHandler()); 45 | 46 | app.listen(3000); 47 | 48 | -------------------------------------------------------------------------------- /model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present NightWorld. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var mongoose = require('mongoose'); 18 | var jwt = require('jsonwebtoken'); 19 | 20 | var Schema = mongoose.Schema; 21 | var model = module.exports; 22 | 23 | model.accessTokenLifetime = 20; 24 | 25 | // JWT secret key 26 | var secretKey = 'sample secret key'; 27 | 28 | // Mongoose schemas 29 | 30 | var OAuthRefreshTokenSchema = new Schema({ 31 | refreshToken: { type: String }, 32 | clientId: { type: String }, 33 | userId: { type: String }, 34 | expires: { type: Date } 35 | }); 36 | 37 | var OAuthClientSchema = new Schema({ 38 | clientId: { type: String }, 39 | clientSecret: { type: String }, 40 | redirectUri: { type: String } 41 | }); 42 | 43 | var OAuthUserSchema = new Schema({ 44 | username: { type: String }, 45 | password: { type: String }, 46 | firstname: { type: String }, 47 | lastname: { type: String }, 48 | email: { type: String, default: '' } 49 | }); 50 | 51 | mongoose.model('OAuthRefreshToken', OAuthRefreshTokenSchema); 52 | mongoose.model('OAuthClient', OAuthClientSchema); 53 | mongoose.model('OAuthUser', OAuthUserSchema); 54 | 55 | var OAuthRefreshTokensModel = mongoose.model('OAuthRefreshToken'), 56 | OAuthClientsModel = mongoose.model('OAuthClient'), 57 | OAuthUsersModel = mongoose.model('OAuthUser'); 58 | 59 | // The following functions customize the behavior of oauth2-server 60 | 61 | /* Token functions */ 62 | model.getAccessToken = function (bearerToken, callback) { 63 | console.log('in getAccessToken (bearerToken: ' + bearerToken + ')'); 64 | 65 | try { 66 | var decoded = jwt.verify(bearerToken, secretKey, { 67 | ignoreExpiration: true //handled by OAuth2 server implementation 68 | }); 69 | callback(null, { 70 | accessToken: bearerToken, 71 | clientId: decoded.sub, 72 | userId: decoded.user, 73 | expires: new Date(decoded.exp * 1000) 74 | }); 75 | } catch(e) { 76 | callback(e); 77 | } 78 | }; 79 | 80 | model.saveAccessToken = function (token, clientId, expires, userId, callback) { 81 | console.log('in saveAccessToken (token: ' + token + 82 | ', clientId: ' + clientId + ', userId: ' + userId.id + 83 | ', expires: ' + expires + ')'); 84 | 85 | //No need to store JWT tokens. 86 | console.log(jwt.decode(token, secretKey)); 87 | 88 | callback(null); 89 | }; 90 | 91 | model.generateToken = function(type, req, callback) { 92 | //Use the default implementation for refresh tokens 93 | console.log('generateToken: ' + type); 94 | if(type === 'refreshToken') { 95 | callback(null, null); 96 | return; 97 | } 98 | 99 | //Use JWT for access tokens 100 | var token = jwt.sign({ 101 | user: req.user.id 102 | }, secretKey, { 103 | expiresIn: model.accessTokenLifetime, 104 | subject: req.client.clientId 105 | }); 106 | 107 | callback(null, token); 108 | } 109 | 110 | model.saveRefreshToken = function (token, clientId, expires, userId, callback) { 111 | console.log('in saveRefreshToken (token: ' + token + 112 | ', clientId: ' + clientId + 113 | ', userId: ' + userId.id + ', expires: ' + expires + ')'); 114 | 115 | var refreshToken = new OAuthRefreshTokensModel({ 116 | refreshToken: token, 117 | clientId: clientId, 118 | userId: userId.id, 119 | expires: expires 120 | }); 121 | 122 | refreshToken.save(callback); 123 | }; 124 | 125 | model.getRefreshToken = function (refreshToken, callback) { 126 | console.log('in getRefreshToken (refreshToken: ' + refreshToken + ')'); 127 | 128 | OAuthRefreshTokensModel.findOne({ refreshToken: refreshToken }, callback); 129 | }; 130 | 131 | model.getClient = function (clientId, clientSecret, callback) { 132 | console.log('in getClient (clientId: ' + clientId + 133 | ', clientSecret: ' + clientSecret + ')'); 134 | if (clientSecret === null) { 135 | return OAuthClientsModel.findOne({ clientId: clientId }, callback); 136 | } 137 | OAuthClientsModel.findOne({ 138 | clientId: clientId, 139 | clientSecret: clientSecret 140 | }, callback); 141 | }; 142 | 143 | model.grantTypeAllowed = function (clientId, grantType, callback) { 144 | console.log('in grantTypeAllowed (clientId: ' + clientId + 145 | ', grantType: ' + grantType + ')'); 146 | 147 | // Authorize all clients to use all grants. 148 | callback(false, true); 149 | }; 150 | 151 | model.getUser = function (username, password, callback) { 152 | console.log('in getUser (username: ' + username + 153 | ', password: ' + password + ')'); 154 | 155 | OAuthUsersModel.findOne({ username: username, password: password }, 156 | function(err, user) { 157 | if(err) return callback(err); 158 | console.log('User id: ' + user._id); 159 | callback(null, user._id); 160 | } 161 | ); 162 | }; 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "refresh-tokens-sample", 3 | "version": "1.0.0", 4 | "description": "A simple refresh token issuing server", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "UNLICENSED", 11 | "dependencies": { 12 | "body-parser": "^1.14.1", 13 | "express": "^4.13.3", 14 | "jsonwebtoken": "^5.4.0", 15 | "mongoose": "^4.1.10", 16 | "oauth2-server": "^2.4.1" 17 | } 18 | } 19 | --------------------------------------------------------------------------------