Nginxとnodejsで認証プロキシを実現する※basic認証じゃないよ！！


こんにちは現在サイバーエージェントのサイバーパルという会社でエンジニアをしている@hidexirです。

# 背景
管理画面の認証を実装するのが少々面倒だったのでロードバランサーで認証を実現しようと思いました。
一般的にベーシック認証やダイナミック認証がnginxを使う場合に思いつきますがさすがにこの認証だ少々怖かったのでアプリケーションで行う方向にしました。あとけっこうまとまっている知見がぱっとみなかったのでまとめました。

作成したのですが今回はそれのまとめです。

# 読んでほしい人
- 一通り認証自体は実装経験がある。
- 管理画面などの認証を一元にしたい。
- nginxの `auth_request`つかってみたい。


管理画面やイントラネットとして活用しているサイトをインターネットに公開する際に認証をアプリケーションに実装するひとが意外に多いです。例えば複数のサブドメインをきって aaa.admin.com bbb.admin.com　で動いている管理画面のアプリケーションがあったとするといちいちアプリ内で認証システムを実装するのはコストが少々高いと思います。
管理するは管理コストが高いですよね。その場合はロードバランサー(今回はnginx)を前段に置き認証をアプリケーションの手前で行うことで。これらの問題を解決します。

# システムの概要
![Untitled Diagram.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257979/c1e90ec9-e2b3-c1bb-a0bd-c00a37978d6f.jpeg)

## 説明
nginxがport`80`にて起動。 認証にて200OKを返したら本命にリクエストを流す。失敗の場合は認証ページにリダイレクト。

`/login` 認証アプリケーションport `3000`
`/auth_request` 認証アプリケーションport `3000`
`/`本命のアプリケーションport `4000` 

# nginx auth_request
```conf
server {
    listen 80;
    server_name  sample.jp;

    # loginページ
    location /login {
      proxy_set_header Host $host;
      proxy_pass_request_body on;
      proxy_no_cache "1";
      proxy_pass http://localhost:3000;
    }

    # auth のtoken確認
    location /auth_request { 
      proxy_pass http://localhost:3000;
      proxy_set_header Host $host;
      proxy_pass_request_body on;
      proxy_no_cache "1";
   }
    # ログイン後のメインアプリ
    location / {
　　　　auth_request /auth_request;　# auth_requestにて上のプロキシさせ200OKを確認してくれる　　　
　　　　error_page 500 = /login;　#login失敗の場合リダイレクト
　　　　proxy_set_header Host $http_host;
　　　　proxy_pass http://localhost:4000;
    }

}
```
# nodejsによる簡易的な認証アプリの説明。
## loginの部分→最後にトークンをCookieにセット
```pug
extends layout

block content
  h1= title
  p #{content}
  form(method="post" action="/login")
    input(type="text" name="userName")
    input(type="text" name="password")
    input(type="submit" name="送信")
```

※実際にはもっと強い暗号やDB問い合わせやJWTなでトークンの安全性は担保してください。

```js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('login', { title: 'Express' });
});
router.post('/', function(req, res, next) {
  if(req.body.userName=="ice" || req.body.password=="man") {
    res.cookie('token', 'yes_token', {maxAge:60000000, httpOnly:false});//ユーザーネームが"ice" パスワードが"man"の場合はいまはcookieに token : yes_token　を返却している。
    res.send("sucsess!");
  }
  res.send("error!");
});

module.exports = router;

```
## トークンの有用性の確認
※実際にはもっと強い暗号やDB問い合わせやJWTなでトークンの安全性は担保してください。

```js
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  if(req.cookies.token == "yes_token"){
    return res.send('sucsess!!') //成功したばあいは200OK
  }
  res.status(500).send('auth failed')//失敗の場合は500 errorとしている
});

module.exports = router;

```
# まとめ
最初にトークンがなくて500でリダイレクトしている304
<img width="636" alt="スクリーンショット 2019-09-28 2.00.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257979/76b7b335-2009-4953-a1b8-e4a6546633ea.png">

実際にtokenが帰ってきてるところです。自分はyes_tokenの代わりにランダムに変更してます。

<img width="712" alt="スクリーンショット 2019-09-28 1.55.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257979/93aa1ffe-8da1-7024-96df-bf07b57bb5ce.png">

本命にgraphqlの管理画面をみれる
<img width="848" alt="スクリーンショット 2019-09-28 1.56.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/257979/e0104a64-31e5-c63e-3a21-120a853d4061.png">

かなり簡単に認証プロキシシステムを作ることができます。