iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 16
0
Modern Web

30 天打造 MERN Stack Boilerplate系列 第 16

Day 16 - Infrastructure - Authentication

Passport

Authentication 是許多 Web 服務都會使用到的基本功能,但每個服務的需求可能又不太一樣,有的人只需要 Basic Auth,有的人需要 Local Auth,也有人需要 Social Auth,Boilerplate 其實不可能做到滿足所有需求,所以使用了 Passport 這套 Library 來統一認證這一個 Infrastructure。

Token-Based Local Authentication & Jwt

雖說大家需要的認證需求都不太一樣,但大部分 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);
};

完整程式碼:src/server/middlewares/passportInit.js

由於 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);
};

完整程式碼:src/server/middlewares/authRequired.js

如此就能透過 Middleware 輕鬆維護 API 的存取權限:

import authRequired from '../middlewares/authRequired';

export default ({ app }) => {
  // ...
  app.get('/api/users/me', authRequired, userController.readSelf);
  // ...
};

完整程式碼:src/server/routes/api.js

Social Authentication

我認為使用 Passport 的最大好處就在於 Social Authentication 有一套標準的實作寫法,無論 Provider 是 Facebook、Twitter 還是 LinkedIn,只要套用對應的 Passport Strategy 就可以很容易實作 Social Authentication。

我們的 Boilerplate 實作了 Facebook 和 LinkedIn 兩個 Providers 的 Social Authentication,但寫法與概念都和 JWT 類似,就不在此細講程式碼,詳見 src/server/middlewares/passportInit.js


上一篇
Day 15 - Infrastructure - Form
下一篇
Day 17 - Infrastructure - Permission Control
系列文
30 天打造 MERN Stack Boilerplate30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言