[node] sailsでPassportを使わずにBasic認証する方法

passportを使ってBasic認証する方法はここに書いてありました。  
とても参考になりました。

[Sails.js + PassportでID/Password認証](http://nantokaworks.com/sails-use-passport01/)

ただ、passportを使うということはブラウザからアクセスするということです。  
クッキーにセッションを保存しておいて、２度目以降はそれで認証する方法です。

しかしAPIを作って公開したい場合には、ブラウザ以外からサーバーにアクセスすることになります。  
ここでは毎回IDとpasswordをサーバーに送って認証するやりかたを書いておきたいと思います。

まず[上記の記事](http://nantokaworks.com/sails-use-passport01/)の前半部分でUserモデルを作って[user:hoge,password:fuga]のアカウントを作る所までを省略します。  
`Passport 承認処理設定`の前まで、つまり`その他hook処理`までを終えている前提で始めます。


> この記事はCoffee-Scriptで記述されています

## RestController.coffee

まずRestController.coffeeを作ります。
```
sails generate controller rest test --coffee
```

test部分を以下のように書き換えます。

```coffeescript
  test:(req,res)->
    name = req.user.username
    res.json hello: name
```

認証がOKならreqオブジェクトからusernameを取り出して、hello:"(name)"というJSONを返すだけです。  
認証が失敗した場合はここまで処理されずにエラーオブジェクトが返ります。

## config/policies.js

最初からある、config/policies.jsファイルを書き換えます。

```js
  RestController: {
    '*': "basicAuth"
  }
```
これで、RestControllerが呼ばれたらその前にbasicAuth関数が呼ばれるようになります。

## api/policies/basicAuth.coffee

api/policies以下に`basicAuth.coffee`を作ります。  
上記のconfig/policies.jsの設定により、RestControllerの前に呼ばれます。
このファイルのポイントは以下の部分です。

```coffeescript
module.exports = (req, res, next)->
```

つまりこの中でIDとpassをチェックして、OKなら`next()`を呼びます。  
`next()`が呼ばれなければRestControllerは呼ばれません。  
その場合は必ず`res.json()`でエラーオブジェクトを返さないと、クライアントはレスポンス待ちの状態になってしまいます。

以下、コードです。

```coffee
bcrypt = require('bcrypt')
findByUsername = (u, fn)->
  User.findOne(username: u).exec (err, user)->
    if (err)
      return fn(null, null)
    else
      return fn(null, user)
isBasicAuth = (username, password, done)->
  process.nextTick ()->
    findByUsername username, (err, user)->
      return done(null, err) if err
      unless user
        return done(null, false, {
          message: 'Unknown user ' + username
          })
      bcrypt.compare password, user.password, (err, res)->
        unless res
          return done(null, false, message: 'Invalid Password' )
        returnUser =
          username: user.username,
          id: user.id
        return done(null, returnUser,message: 'Logged In Successfully')
module.exports = (req, res, next)->
  try
  	# エラー定義
    e = errors:[message: 'Bad Authentication data',code: 215]
    if s = req.get('authorization')
      authStr = s.replace("Basic ",'')
      auth = new Buffer(authStr, 'base64').toString().split(":")
      # auth = [ 'username11', 'password11' ]
      isBasicAuth auth[0],auth[1],(err,user,msg)->
        console.log msg if msg
        if err
          return res.json(err)
        else if user
          req.user = user
          return next()
        else
          return res.json(e)
    else
      return res.json(e)
  catch err
    return res.json(err)
```

[ここ](http://nantokaworks.com/sails-use-passport01/)に書いてあるpassportの設定方法を流用していますが、ポイントがいくつかあるので解説します。

> `sails lift`した場合に、ブラウザで呼び出すには`http://localhost:1337/rest/test`にアクセスします。  
> これをcurlコマンドで呼び出す場合は  

> `curl -u "hoge:fuga" http://localhost:1337/rest/test`

> となります。  
> -uオプションの部分でidとパスワードを設定できます。


クライアントから送られてきたidとパスワードは、サーバー側(sails側)ではreq.header.authorizationにあります。  
req.getメソッドで取り出せます。

```coffeescript
console.log req.get('authorization')
# 'Basic aG9nZTpmdWdh'
```

しかしこのままではbase64でエンコードされていますのでデコードします。

```coffeescript
authStr = s.replace("Basic ",'')
auth = new Buffer(authStr, 'base64').toString().split(":")
# auth = [ 'username11', 'password11' ]
```

これでクライアントから送られてきたidとパスワードが取得出来ました。  
あとはDBからhogeユーザーの情報を取り出して`bcrypt.compare()`でpasswordが合っているか照合するだけです。

isBasicAuthメソッドでuser情報が返ってきますので、reqオブジェクトに追加しておきます。

```coffeescript
req.user = user
return next()
```

これで認証がOKとなり、RestControllerへ処理が流れます。  
RestControllerからはreq.userでユーザーの情報が取り出せます。

## 検証

sails liftして別のターミナルから呼んでみます。

```bash
curl -u "hoge:fuga" http://localhost:1337/rest/test
{
  "hello": "hoge"
}
```

わざと認証失敗させてみます。

```zsh
curl -u "hogeo:fugao" http://localhost:1337/rest/test
{
  "errors": [
    {
      "message": "Bad Authentication data",
      "code": 215
    }
  ]
}
```

以上です。