Authentication 是許多 Web 服務都會使用到的基本功能,但每個服務的需求可能又不太一樣,有的人只需要 Basic Auth,有的人需要 Local Auth,也有人需要 Social Auth,Boilerplate 其實不可能做到滿足所有需求,所以使用了 Passport 這套 Library 來統一認證
這一個 Infrastructure。
雖說大家需要的認證需求都不太一樣,但大部分 Web 服務都還是會需要基本的站內註冊及登入登出,所以 Boilerplate 有幫各位實作了 Local Authentication 的功能。
許多實作 Local Authentication 的入門教學都是 Session-Based,但我個人非常排斥使用 Session,因為 HTTP 是一個 Stateless 的協定,而 Session 是個 Stateful 的東西,違反了 HTTP 本身的設計原則,而且當 Server Scale Up 時,還要有管理 Session 的機制,徒增營運上的困擾,所以不如打從一開始就棄用。
我們的 Boilerplate 採用的是 Token-Based 的 Authentication,這裡所謂的 Token 指的是 JWT(JSON Web Tokens),這是目前 Modern Web 中很熱門的認證方式,而且是 Stateless。因此實作上搭配 passport-jwt 這個 Passport Strategy,撰寫了 passportInit
這個 Middleware:
let cookieExtractor = (req) => {
return req.store.getState().cookies.token;
};
export default (req, res, next) => {
passport.use(new JwtStrategy({
jwtFromRequest: cookieExtractor,
secretOrKey: configs.jwt.authentication.secret,
}, (jwtPayload, done) => {
// this callback is invoked only when jwt token is correctly decoded
User.findById(jwtPayload._id, handleDbError(res)((user) => {
done(null, user);
}));
}));
passport.initialize()(req, res, next);
};
由於 JWT 是夾帶在 Cookie 中,所以可以看到程式碼中使用了 cookieExtractor
,從 Store 中的 cookies 讀取 Token。
另外我們還必須要有 authRequired
這樣的 Middleware 讓我們確保某些 API Path 只有已登入的使用者可以存取:
export default (req, res, next) => {
passport.authenticate(
'jwt',
{ session: false },
handleError(res)((user, info) => {
handlePassportError(res)((user) => {
if (!user) {
res.pushError(Errors.USER_UNAUTHORIZED);
return res.errors();
}
req.user = user;
next();
})(info, user);
})
)(req, res, next);
};
如此就能透過 Middleware 輕鬆維護 API 的存取權限:
import authRequired from '../middlewares/authRequired';
export default ({ app }) => {
// ...
app.get('/api/users/me', authRequired, userController.readSelf);
// ...
};
完整程式碼:src/server/routes/api.js
我認為使用 Passport 的最大好處就在於 Social Authentication 有一套標準的實作寫法,無論 Provider 是 Facebook、Twitter 還是 LinkedIn,只要套用對應的 Passport Strategy 就可以很容易實作 Social Authentication。
我們的 Boilerplate 實作了 Facebook 和 LinkedIn 兩個 Providers 的 Social Authentication,但寫法與概念都和 JWT 類似,就不在此細講程式碼,詳見 src/server/middlewares/passportInit.js。