Last active
June 21, 2017 07:25
-
-
Save pjchender/9d6303296448862b75460cf335bd3902 to your computer and use it in GitHub Desktop.
Dive into Passport JS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./node_modules/passport/lib/middleware/authenticate.js | |
/** | |
* Module dependencies. | |
*/ | |
var http = require('http'), | |
IncomingMessageExt = require('../http/request'), | |
AuthenticationError = require('../errors/authenticationerror') | |
/** | |
* Authenticates requests. | |
* | |
* Applies the `name`ed strategy (or strategies) to the incoming request, in | |
* order to authenticate the request. If authentication is successful, the user | |
* will be logged in and populated at `req.user` and a session will be | |
* established by default. If authentication fails, an unauthorized response | |
* will be sent. | |
* | |
* Options: | |
* - `session` Save login state in session, defaults to _true_ | |
* - `successRedirect` After successful login, redirect to given URL | |
* - `failureRedirect` After failed login, redirect to given URL | |
* - `assignProperty` Assign the object provided by the verify callback to given property | |
* | |
* An optional `callback` can be supplied to allow the application to overrride | |
* the default manner in which authentication attempts are handled. The | |
* callback has the following signature, where `user` will be set to the | |
* authenticated user on a successful authentication attempt, or `false` | |
* otherwise. An optional `info` argument will be passed, containing additional | |
* details provided by the strategy's verify callback. | |
* | |
* app.get('/protected', function(req, res, next) { | |
* passport.authenticate('local', function(err, user, info) { | |
* if (err) { return next(err) } | |
* if (!user) { return res.redirect('/signin') } | |
* res.redirect('/account'); | |
* })(req, res, next); | |
* }); | |
* | |
* Note that if a callback is supplied, it becomes the application's | |
* responsibility to log-in the user, establish a session, and otherwise perform | |
* the desired operations. | |
* | |
* Examples: | |
* | |
* passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login' }); | |
* | |
* passport.authenticate('basic', { session: false }); | |
* | |
* passport.authenticate('twitter'); | |
* | |
* @param {String|Array} name | |
* @param {Object} options | |
* @param {Function} callback | |
* @return {Function} | |
* @api public | |
*/ | |
module.exports = function authenticate (passport, name, options, callback) { | |
if (typeof options === 'function') { | |
callback = options | |
options = {} | |
} | |
options = options || {} | |
var multi = true | |
// Cast `name` to an array, allowing authentication to pass through a chain of | |
// strategies. The first strategy to succeed, redirect, or error will halt | |
// the chain. Authentication failures will proceed through each strategy in | |
// series, ultimately failing if all strategies fail. | |
// | |
// This is typically used on API endpoints to allow clients to authenticate | |
// using their preferred choice of Basic, Digest, token-based schemes, etc. | |
// It is not feasible to construct a chain of multiple strategies that involve | |
// redirection (for example both Facebook and Twitter), since the first one to | |
// redirect will halt the chain. | |
if (!Array.isArray(name)) { | |
name = [ name ] | |
multi = false | |
} | |
// NOTE: 主要的 authenticate function | |
return function authenticate (req, res, next) { | |
if (http.IncomingMessage.prototype.logIn && | |
http.IncomingMessage.prototype.logIn !== IncomingMessageExt.logIn) { | |
require('../framework/connect').__monkeypatchNode() | |
} | |
// accumulator for failures from each strategy in the chain | |
var failures = [] | |
// NOTE: allFailed function | |
function allFailed () { | |
if (callback) { | |
if (!multi) { | |
return callback(null, false, failures[0].challenge, failures[0].status) | |
} else { | |
var challenges = failures.map(function (f) { return f.challenge }) | |
var statuses = failures.map(function (f) { return f.status }) | |
return callback(null, false, challenges, statuses) | |
} | |
} | |
// Strategies are ordered by priority. For the purpose of flashing a | |
// message, the first failure will be displayed. | |
var failure = failures[0] || {}, | |
challenge = failure.challenge || {}, | |
msg | |
// NOTE: 如果有設定 failureFlash | |
if (options.failureFlash) { | |
var flash = options.failureFlash | |
if (typeof flash === 'string') { | |
flash = { type: 'error', message: flash } | |
} | |
flash.type = flash.type || 'error' | |
var type = flash.type || challenge.type || 'error' | |
msg = flash.message || challenge.message || challenge | |
if (typeof msg === 'string') { | |
req.flash(type, msg) | |
} | |
} | |
// NOTE: 如果有設定 failureMessage | |
if (options.failureMessage) { | |
msg = options.failureMessage | |
if (typeof msg === 'boolean') { | |
msg = challenge.message || challenge | |
} | |
if (typeof msg === 'string') { | |
req.session.messages = req.session.messages || [] | |
req.session.messages.push(msg) | |
} | |
} | |
// NOTE: 如果有設定 failureRedirect | |
if (options.failureRedirect) { | |
return res.redirect(options.failureRedirect) | |
} | |
// When failure handling is not delegated to the application, the default | |
// is to respond with 401 Unauthorized. Note that the WWW-Authenticate | |
// header will be set according to the strategies in use (see | |
// actions#fail). If multiple strategies failed, each of their challenges | |
// will be included in the response. | |
var rchallenge = [], | |
rstatus, status | |
for (var j = 0, len = failures.length; j < len; j++) { | |
failure = failures[j] | |
challenge = failure.challenge | |
status = failure.status | |
rstatus = rstatus || status | |
if (typeof challenge === 'string') { | |
rchallenge.push(challenge) | |
} | |
} | |
// NOTE: 如果 rstatus 不存在,則給 401(Unauthorized) | |
res.statusCode = rstatus || 401 | |
if (res.statusCode == 401 && rchallenge.length) { | |
res.setHeader('WWW-Authenticate', rchallenge) | |
} | |
// 如果有設定 failWithError | |
if (options.failWithError) { | |
return next(new AuthenticationError(http.STATUS_CODES[res.statusCode], rstatus)) | |
} | |
// 以語意化方式給出最後的結果(預設 401 Unauthorized) | |
res.end(http.STATUS_CODES[res.statusCode]) | |
} | |
(function attempt (i) { | |
var layer = name[i] | |
// If no more strategies exist in the chain, authentication has failed. | |
if (!layer) { return allFailed() } | |
// Get the strategy, which will be used as prototype from which to create | |
// a new instance. Action functions will then be bound to the strategy | |
// within the context of the HTTP request/response pair. | |
var prototype = passport._strategy(layer) | |
if (!prototype) { return next(new Error('Unknown authentication strategy "' + layer + '"')) } | |
var strategy = Object.create(prototype) | |
// ----- BEGIN STRATEGY AUGMENTATION ----- | |
// Augment the new strategy instance with action functions. These action | |
// functions are bound via closure the the request/response pair. The end | |
// goal of the strategy is to invoke *one* of these action methods, in | |
// order to indicate successful or failed authentication, redirect to a | |
// third-party identity provider, etc. | |
/** | |
* Authenticate `user`, with optional `info`. | |
* | |
* Strategies should call this function to successfully authenticate a | |
* user. `user` should be an object supplied by the application after it | |
* has been given an opportunity to verify credentials. `info` is an | |
* optional argument containing additional user information. This is | |
* useful for third-party authentication strategies to pass profile | |
* details. | |
* | |
* @param {Object} user | |
* @param {Object} info | |
* @api public | |
*/ | |
strategy.success = function (user, info) { | |
if (callback) { | |
return callback(null, user, info) | |
} | |
info = info || {} | |
var msg | |
if (options.successFlash) { | |
var flash = options.successFlash | |
if (typeof flash === 'string') { | |
flash = { type: 'success', message: flash } | |
} | |
flash.type = flash.type || 'success' | |
var type = flash.type || info.type || 'success' | |
msg = flash.message || info.message || info | |
if (typeof msg === 'string') { | |
req.flash(type, msg) | |
} | |
} | |
if (options.successMessage) { | |
msg = options.successMessage | |
if (typeof msg === 'boolean') { | |
msg = info.message || info | |
} | |
if (typeof msg === 'string') { | |
req.session.messages = req.session.messages || [] | |
req.session.messages.push(msg) | |
} | |
} | |
if (options.assignProperty) { | |
req[options.assignProperty] = user | |
return next() | |
} | |
req.logIn(user, options, function (err) { | |
if (err) { return next(err) } | |
function complete () { | |
if (options.successReturnToOrRedirect) { | |
var url = options.successReturnToOrRedirect | |
if (req.session && req.session.returnTo) { | |
url = req.session.returnTo | |
delete req.session.returnTo | |
} | |
return res.redirect(url) | |
} | |
if (options.successRedirect) { | |
return res.redirect(options.successRedirect) | |
} | |
next() | |
} | |
if (options.authInfo !== false) { | |
passport.transformAuthInfo(info, req, function (err, tinfo) { | |
if (err) { return next(err) } | |
req.authInfo = tinfo | |
complete() | |
}) | |
} else { | |
complete() | |
} | |
}) | |
} | |
/** | |
* Fail authentication, with optional `challenge` and `status`, defaulting | |
* to 401. | |
* | |
* Strategies should call this function to fail an authentication attempt. | |
* | |
* @param {String} challenge | |
* @param {Number} status | |
* @api public | |
*/ | |
strategy.fail = function (challenge, status) { | |
if (typeof challenge === 'number') { | |
status = challenge | |
challenge = undefined | |
} | |
// push this failure into the accumulator and attempt authentication | |
// using the next strategy | |
failures.push({ challenge: challenge, status: status }) | |
attempt(i + 1) | |
} | |
/** | |
* Redirect to `url` with optional `status`, defaulting to 302. | |
* | |
* Strategies should call this function to redirect the user (via their | |
* user agent) to a third-party website for authentication. | |
* | |
* @param {String} url | |
* @param {Number} status | |
* @api public | |
*/ | |
strategy.redirect = function (url, status) { | |
// NOTE: Do not use `res.redirect` from Express, because it can't decide | |
// what it wants. | |
// | |
// Express 2.x: res.redirect(url, status) | |
// Express 3.x: res.redirect(status, url) -OR- res.redirect(url, status) | |
// - as of 3.14.0, deprecated warnings are issued if res.redirect(url, status) | |
// is used | |
// Express 4.x: res.redirect(status, url) | |
// - all versions (as of 4.8.7) continue to accept res.redirect(url, status) | |
// but issue deprecated versions | |
res.statusCode = status || 302 | |
res.setHeader('Location', url) | |
res.setHeader('Content-Length', '0') | |
res.end() | |
} | |
/** | |
* Pass without making a success or fail decision. | |
* | |
* Under most circumstances, Strategies should not need to call this | |
* function. It exists primarily to allow previous authentication state | |
* to be restored, for example from an HTTP session. | |
* | |
* @api public | |
*/ | |
strategy.pass = function () { | |
next() | |
} | |
/** | |
* Internal error while performing authentication. | |
* | |
* Strategies should call this function when an internal error occurs | |
* during the process of performing authentication; for example, if the | |
* user directory is not available. | |
* | |
* @param {Error} err | |
* @api public | |
*/ | |
strategy.error = function (err) { | |
if (callback) { | |
return callback(err) | |
} | |
next(err) | |
} | |
// ----- END STRATEGY AUGMENTATION ----- | |
strategy.authenticate(req, options) | |
})(0) // attempt | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./node_modules/passport-jwt/lib/strategy.js | |
var passport = require('passport-strategy') | |
, auth_hdr = require('./auth_header') | |
, util = require('util') | |
, url = require('url'); | |
/** | |
* Strategy constructor | |
* | |
* @param options | |
* secretOrKey: (REQUIRED) String or buffer containing the secret or PEM-encoded public key | |
* jwtFromRequest: (REQUIRED) Function that accepts a reqeust as the only parameter and returns the either JWT as a string or null | |
* issuer: If defined issuer will be verified against this value | |
* audience: If defined audience will be verified against this value | |
* algorithms: List of strings with the names of the allowed algorithms. For instance, ["HS256", "HS384"]. | |
* ignoreExpiration: if true do not validate the expiration of the token. | |
* passReqToCallback: If true the, the verify callback will be called with args (request, jwt_payload, done_callback). | |
* @param verify - Verify callback with args (jwt_payload, done_callback) if passReqToCallback is false, | |
* (request, jwt_payload, done_callback) if true. | |
*/ | |
function JwtStrategy(options, verify) { | |
passport.Strategy.call(this); | |
this.name = 'jwt'; | |
this._secretOrKey = options.secretOrKey; | |
if (!this._secretOrKey) { | |
throw new TypeError('JwtStrategy requires a secret or key'); | |
} | |
this._verify = verify; | |
if (!this._verify) { | |
throw new TypeError('JwtStrategy requires a verify callback'); | |
} | |
this._jwtFromRequest = options.jwtFromRequest; | |
if (!this._jwtFromRequest) { | |
throw new TypeError('JwtStrategy requires a function to retrieve jwt from requests (see option jwtFromRequest)'); | |
} | |
this._passReqToCallback = options.passReqToCallback; | |
this._verifOpts = {}; | |
if (options.issuer) { | |
this._verifOpts.issuer = options.issuer; | |
} | |
if (options.audience) { | |
this._verifOpts.audience = options.audience; | |
} | |
if (options.algorithms) { | |
this._verifOpts.algorithms = options.algorithms; | |
} | |
if (options.ignoreExpiration != null) { | |
this._verifOpts.ignoreExpiration = options.ignoreExpiration; | |
} | |
}; | |
util.inherits(JwtStrategy, passport.Strategy); | |
/** | |
* Allow for injection of JWT Verifier. | |
* | |
* This improves testability by allowing tests to cleanly isolate failures in the JWT Verification | |
* process from failures in the passport related mechanics of authentication. | |
* | |
* Note that this should only be replaced in tests. | |
*/ | |
JwtStrategy.JwtVerifier = require('./verify_jwt'); | |
/** | |
* Authenticate request based on JWT obtained from header or post body | |
*/ | |
JwtStrategy.prototype.authenticate = function(req, options) { | |
var self = this; | |
var token = self._jwtFromRequest(req); | |
if (!token) { | |
return self.fail(new Error("No auth token")); | |
} | |
// Verify the JWT | |
JwtStrategy.JwtVerifier(token, this._secretOrKey, this._verifOpts, function(jwt_err, payload) { | |
if (jwt_err) { | |
return self.fail(jwt_err); | |
} else { | |
// Pass the parsed token to the user | |
var verified = function(err, user, info) { | |
if(err) { | |
// NOTE: 如果有錯誤,進到 self.error | |
return self.error(err); | |
} else if (!user) { | |
// NOTE: 如果找不到使用者,進到 self.fail | |
return self.fail(info); | |
// NOTE: 如果都沒有問題,進到 self.success | |
} else { | |
return self.success(user, info); | |
} | |
}; | |
try { | |
if (self._passReqToCallback) { | |
self._verify(req, payload, verified); | |
} else { | |
self._verify(payload, verified); | |
} | |
} catch(ex) { | |
self.error(ex); | |
} | |
} | |
}); | |
}; | |
/** | |
* Export the Jwt Strategy | |
*/ | |
module.exports = JwtStrategy; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
程式執行的順序
先進到
Passport Middleware Strategy
進到 JWT Strategy