Skip to content

Commit

Permalink
First commit - working passport oauth
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholastay committed Mar 12, 2016
0 parents commit 7fb850c
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
npm-debug.log*
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# passport-discord

Passport strategy for authentication with [Discord](http://discordapp.com) through the OAuth 2.0 API.

At time of writing there is no official page/documentation for this, so information can be read off the example project, such as how to set up an API application [here](http://github.com/vishnevskiy/discord-oauth2-example).

## Usage
`npm install passport-discord --save`

#### Configure Strategy
The Discord authentication strategy authenticates users via a Discord user account and OAuth 2.0 token(s). A Discord API client ID, secret and redirect URL must be supplied when using this strategy. The strategy also requires a `verify` callback, which receives the access token and an optional refresh token, as well as a `profile` which contains the authenticated Discord user's profile. The `verify` callback must also call `cb` providing a user to complete the authentication.

```javascript
var DiscordStrategy = require('passport-discord').Strategy;

passport.use(new DiscordStrategy(
{
clientID: 'id',
clientSecret: 'secret',
callbackURL: 'callbackURL'
},
function(accessToken, refreshToken, profile, cb) {
User.findOrCreate({ discordId: profile.id }, function(err, user) {
return cb(err, user);
});
}
));
```

#### Authentication Requests
Use `passport.authenticate()`, and specify the `'discord'` strategy to authenticate requests.

Foe example, as a route middleware in an Express app:

```javascript
app.get('/auth/discord', passport.authenticate('discord'));
app.get('/auth/discord/callback', passport.authenticate('discord', {
failureRedirect: '/'
}), function(req, res) {
res.redirect('/secretstuff') // Successful auth
});
```


## Examples
An Express server example can be found in the `/example` directory. Be sure to `npm install` in that directory to get the dependencies.

## Credits
* Jared Hanson - used passport-github to understand passport more and kind of as a base.
17 changes: 17 additions & 0 deletions example/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "passport-discord-example",
"version": "0.1.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "Nicholas Tay <[email protected]> (http://nicholastay.github.io)",
"license": "ISC",
"dependencies": {
"express": "^4.13.4",
"express-session": "^1.13.0",
"passport": "^0.3.2"
}
}
57 changes: 57 additions & 0 deletions example/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
var express = require('express')
, session = require('express-session')
, passport = require('passport')
, Strategy = require('../lib').Strategy
, app = express();

passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});

var scopes = ['identify', 'email', /* 'connections', (it is currently broken) */ 'guilds', 'guilds.join'];

passport.use(new Strategy({
clientID: '',
clientSecret: '',
callbackURL: 'http://localhost:5000/callback',
scope: scopes
}, function(accessToken, refreshToken, profile, done) {
process.nextTick(function() {
return done(null, profile);
});
}));

app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.get('/', passport.authenticate('discord', { scope: scopes }), function(req, res) {});
app.get('/callback',
passport.authenticate('discord', { failureRedirect: '/' }), function(req, res) { res.redirect('/info') } // auth success
);
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
app.get('/info', checkAuth, function(req, res) {
//console.log(req.user)
res.json(req.user);
});


function checkAuth(req, res, next) {
if (req.isAuthenticated()) return next();
res.send('not logged in :(');
}


app.listen(5000, function (err) {
if (err) return console.log(err)
console.log('Listening at http://localhost:5000/')
})
14 changes: 14 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Module dependencies.
*/
var Strategy = require('./strategy');

/**
* Expose `Strategy` directly from package.
*/
exports = module.exports = Strategy;

/**
* Export constructors.
*/
exports.Strategy = Strategy;
109 changes: 109 additions & 0 deletions lib/strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* Dependencies
*/
var OAuth2Strategy = require('passport-oauth2')
, InternalOAuthError = require('passport-oauth2').InternalOAuthError
, util = require('util');

/**
* `Strategy` constructor.
*
* The Discord authentication strategy authenticates requests by delegating to
* Discord via the OAuth2.0 protocol
*
* Applications must supply a `verify` callback which accepts an `accessToken`,
* `refreshToken` and service-specific `profile`, and then calls the `cb`
* callback supplying a `user`, which should be set to `false` if the
* credentials are not valid. If an exception occured, `err` should be set.
*
* Options:
* - `clientID` OAuth ID to discord
* - `clientSecret` OAuth Secret to verify client to discord
* - `callbackURL` URL that discord will redirect to after auth
* - `scope` Array of permission scopes to request
* Valid discord scopes include: 'identity', 'email', 'connections', 'guilds', 'guilds.join'
*
* @constructor
* @param {object} options
* @param {function} verify
* @access public
*/
function Strategy(options, verify) {
options = options || {};
options.authorizationURL = options.authorizationURL || 'https://discordapp.com/api/oauth2/authorize';
options.tokenURL = options.tokenURL || 'https://discordapp.com/api/oauth2/token';
options.scopeSeparator = options.scopeSeparator || ' ';

OAuth2Strategy.call(this, options, verify);
this.name = 'discord';
this._oauth2.useAuthorizationHeaderforGET(true);
}

/**
* Inherits from `OAuth2Strategy`
*/
util.inherits(Strategy, OAuth2Strategy);

/**
* Retrieve user profile from Discord.
*
* This function constructs a normalized profile, with the following properties:
*
* - `something` ayy lmao
*
* @param {string} accessToken
* @param {function} done
* @access protected
*/
Strategy.prototype.userProfile = function(accessToken, done) {
var self = this;
this._oauth2.get('https://discordapp.com/api/users/@me', accessToken, function(err, body, res) {
if (err) {
return done(new InternalOAuthError('Failed to fetch the user profile.', err))
}

try {
var parsedData = JSON.parse(body);
}
catch (e) {
return done(new Error('Failed to parse the user profile.'));
}

var profile = parsedData; // has the basic user stuff
profile.provider = 'discord';

self.checkScope('connections', accessToken, function(errx, connections) {
if (errx) done(errx);
if (connections) profile.connections = connections;
self.checkScope('guilds', accessToken, function(erry, guilds) {
if (erry) done(erry);
if (guilds) profile.guilds = guilds;

return done(null, profile)
});
});
});
};

Strategy.prototype.checkScope = function(scope, accessToken, cb) {
if (this._scope && this._scope.indexOf(scope) !== -1) {
this._oauth2.get('https://discordapp.com/api/users/@me/' + scope, accessToken, function(err, body, res) {
if (err) return cb(new InternalOAuthError('Failed to fetch user\'s ' + scope, err));
try {
var json = JSON.parse(body);
}
catch (e) {
return cb(new Error('Failed to parse user\'s ' + scope));
}
cb(null, json);
});
} else {
cb(null, null);
}
}


/**
* Expose `Strategy`.
*/
module.exports = Strategy;
30 changes: 30 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "passport-discord",
"version": "0.1.0",
"description": "Passport strategy for authentication with Discord (discordapp.com)",
"main": "lib/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nicholastay/passport-discord.git"
},
"keywords": [
"passport",
"discord",
"auth",
"authentication",
"authn",
"identity"
],
"author": "Nicholas Tay <[email protected]> (http://nicholastay.github.io)",
"license": "ISC",
"bugs": {
"url": "https://github.com/nicholastay/passport-discord/issues"
},
"homepage": "https://github.com/nicholastay/passport-discord#readme",
"dependencies": {
"passport-oauth2": "^1.2.0"
}
}

0 comments on commit 7fb850c

Please sign in to comment.