前提
Rails+Reactでサービス開発をはじめ、まずはユーザ認証を実装しようとしていた。
Rails側はdevise+devise_token_authで認証APIを実装、React側はAPIにPOSTでリクエストを送って
トークンを取得し、LocalStorageに保存することでユーザのログイン・未ログインを実装しようとしていた。
APIへのアクセスにsuperagentを、CORS対策にrack-corsを利用した。
開発環境
- Rails (5.1.5)
- React (16.2.0)
- superagent (3.8.2)
- rack-cors (1.0.2)
発生した問題
ReactからSuperAgentを利用してAPIにPOSTすると、期待したJSONが帰ってくるのに、ヘッダのaccess-token
が取得できなかった。
実装
Rails側
こちらの記事を参考に、email
とpassword
を含んだJSONをPOSTで送るとトークンを返すように実装をした。
devise token auth を使って簡単に早くAPIを作る 1 | せんべいって美味しいよね
React側のリクエスト
指定したURLにユーザから入力されたemail
とpassword
をPOSTするだけ。
※ フォームの作成にmaterial-uiを使用していますが、全く関係ないので気にしないでください🙇
import React, {Component} from 'react'
import request from 'superagent'
import TextField from 'material-ui/TextField'
import FlatButton from 'material-ui/FlatButton'
const baseURL = 'http://localhost:3000' // 開発環境に合わせて設定
export default class Login extends Component {
constructor (props) {
super(props)
this.state = { email: '', password: '' }
}
// api呼び出し
api (command) {
request
.post(baseURL + '/api/' + command)
.set('Content-Type', 'application/json')
.send({
email: this.state.email,
password: this.state.password
})
.end((err, res) => {
if (err) return
if (res.headers['access-token']) {
// 認証トークンをlocalStorageに保存
window.localStorage['access-token'] = res.headers['access-token']
return
}
})
}
render() {
const changed = (name, e) => this.setState({[name]: e.target.value})
return (
<div>
<h1>ログイン</h1>
<TextField
value={this.state.email}
floatingLabelText="email"
onChange={e => changed('email', e)}
/><br />
<TextField
value={this.state.password}
floatingLabelText="パスワード"
type="password"
onChange={e => changed('password', e)}
/><br />
<FlatButton label="ログイン" onClick={e => this.api('auth/sign_in')} />
</div>
)
}
}
res.headers
にaccess-token
が含まれていて、取得できると思っていたらできなかった。
ちなみにChromeの開発者ツールのNetworkから見ると、しっかりとHeadersにaccess-token
が含まれていた。
原因は、レスポンスヘッダに含まれる独自のHTTPレスポンスヘッダを取得しようとすると、ブラウザがセキュアではないヘッダにアクセスしようとしているとみなしてアクセス許可をしてくれないことだった。
さらに、Access-Control-Expose-Headers
というレスポンスヘッダを設定すると解決しそうなこともわかった。
解決方法
rack-cors
の設定で許可するレスポンスヘッダを指定することができた。
rack-cors
のexpose
の指定で許可するヘッダを指定する(Access-Control-Expose-Headersを設定する)。
config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:8080' # 開発環境に合わせて設定
resource '*', :headers => :any, :methods => [:get, :post, :options], :expose => ['access-token']
end
end
この設定を追加することで無事React側でaccess-token
を取得することができました。
参考記事
CORSまとめ#レスポンスに独自のHTTPレスポンスヘッダを追加してブラウザから読み出したい場合 - Qiita
Missing response headers in response.headers object · Issue #713 · visionmedia/superagent
Expose HTTP Headers in CORS | Jake Trent