モバイルアプリのユーザ認証方法についてまとめてみた

# 追記 (2018-10-08)

4年以上前に書いた記事ですが、Access Token として JWT を利用することは非推奨なようなので、お詫びして修正致します。

- 参考: [どうしてリスクアセスメントせずに JWT をセッションに使っちゃうわけ？](https://co3k.org/blog/why-do-you-use-jwt-for-session)


# 概要

みんなやってるはずなんだけど、あまりまとまった情報がなかったので書いてみます。認証周りはセキュリティを気にして、みんな書きたがらないのかな？それとも私の調べ方が悪かっただけ？マサカリお待ちしてます。

# 認証の基本方針

```
+--------+                                +--------+
|        |                                |        |
|        |----(1) Credential ------------>|        |
|        |                                |        |
|        |<---(2) Access Token -----------|        |
|        |                                |        |
| Client |                                | Server |
|        |                                |        |
|        |----(3) API Call with Token --->|        |
|        |                                |        |
|        |<---(4) API Response -----------|        |
|        |                                |        |
+--------+                                +--------+

```
ざっくり言うと、
 1. クライアントからサーバに認証情報を渡して、
2. サーバで認証情報を検証して、Access Token を発行し、
3. クライアントは API Call で Access Token を利用、
4. サーバは Access Token を検証して、API のレスポンスを返す

となります。

Cookie を使う方法もあるけど、サーバで認証用の Access Token を発行するのが基本です。以下、それぞれのステップについて説明していきます。

# 何を credential とするか
よくある credential （認証情報）は、以下のようなものでしょうか。

## username, password

外部の ID Provider (Facebook, Twitter など)での認証に頼らない場合に利用します。Legacy な認証方式ですが、OAuth 2.0 でも Resource Owner Password Credentials Grant として定義されているものです。

サーバは username, password を検証し、有効であれば、クライアントに Access Token を返します。（必要に応じて、ユーザを新規に作成します。以降の項では省略。）

## 外部の ID Provider の Access Token

何かしらの方法(OAuth2等)で外部の IdP からユーザの Access Token を取得し、それを credential として利用します。（上の図では Client から Server に Credential を渡していますが、OAuth2 の Authorization Code Grant ではサーバが IdP から直接 Access Token を受け取ります）

サーバは（外部 IdP の） Access Token を検証し、有効であれば、クライアントに（自分のサービスの） Access Token を返します。

このとき、（外部 IdP の） Access Token が単に使えるということだけでなく、トークンの発行先が自身のアプリであることも確認が必要です（[token 置換攻撃対策](http://oauth.jp/blog/2012/02/08/ios-sdk/)）。Facebook であれば [App API](https://developers.facebook.com/docs/graph-api/reference/v2.0/app)が使えますし、Google なら [tokeninfo API](https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken)が使えます。


## 外部の ID Provider の ID Token

OpenID Connect に対応している IdP (Google, mixi, Yahoo など)であれば、ID Token を credential として利用することが出来ます。

詳細は [mixi の認証資料](http://developer.mixi.co.jp/connect/mixi_graph_api/api_auth/)などを見ていただくとして、サーバは ID Token を検証し、有効であれば、クライアントに Access Token を返します。

なお、 OpenID Connect の仕様をもう少ししっかり利用すると、任意の IdP を利用して認証することが出来るようになります。([OpenID Connect Discovery](http://openid.net/specs/openid-connect-discovery-1_0.html) で IdP を探して、[OpenID Connect Dynamic Client Registration](http://openid.net/specs/openid-connect-registration-1_0.html) でサービス用の client_id を取得し、認証。)

# どのような Token をクライアントに返すか

外部の IdP の話が出てきたのでちょっと混乱しちゃうかもしれませんが、いったん Credential の検証ができたら、今度は **サーバ自身が IdP として** クライアントに Access Token を払い出します。

Access Token の払い出し方法としては、大きく以下の2つの方法があるでしょうか。

## Token の種類
### ランダムな文字列を DB に保存する

サーバでランダムな文字列を発行して、DB に保存し、Access Token として利用する方法です。
最低限、以下のような項目が必要になります。

- User ID
- Access Token
- 有効期限

### ~~JWT を利用する~~

~~OAuth 2.0 の文脈で仕様策定された、[JWT (JSON Web Token)](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-23)という仕様があります。先ほど出てきた ID Token も JWT の一種なのですが、JWT は Access Token として利用することも出来ます。~~

~~JWT (というか Signed JWT。詳しくは [nov さんの記事](http://oauth.jp/blog/2012/10/26/json-web-token-jwt/)参照) には署名がついているので、共通鍵（HMACによる署名の場合)や公開鍵（RSA署名の場合）があれば、Token の検証ができます。また、Token の中に user_id や expiration time などの情報を埋め込めるので、DB による Token の管理が不要です。~~

この辺りの話は、以下の記事が詳しいです。
- [10 Things You Should Know about Tokens](https://auth0.com/blog/2014/01/27/ten-things-you-should-know-about-tokens-and-cookies/)

また、Django ではそのためのライブラリが実装されてます。
- [Django REST framework JWT Auth](https://github.com/GetBlimp/django-rest-framework-jwt)

### 追記 (2018-10-08)

以下の理由から、 Access Token として JWT を利用することは推奨されません。ご注意ください。

- サーバ側でのセッションの無効化ができない
- ユーザのログアウト時にセッションの無効化ができない

記事の執筆当初は、 Token の検証だけに注目していて、柔軟な revoke の観点が抜けておりました。

また、 JWT を正しく運用すべきなのはもちろんのこと、暗号鍵を利用する性質上、秘密鍵の管理が極めて重要となります。(SSL など、暗号化技術を用いているその他の技術でも同じですが、セッションの管理は影響範囲が大きいです)

- 参考: [どうしてリスクアセスメントせずに JWT をセッションに使っちゃうわけ？](https://co3k.org/blog/why-do-you-use-jwt-for-session)

結局は、前項の「ランダムな文字列を DB に保存する」方式が良さそうです。


## 有効期限の管理

Token の有効期限が切れる前の早い段階で、新しいTokenを取得しなおしてもらいましょう。 ~~JWTの場合は、その情報を Token に入れられるのですが、~~ ランダムな文字列を Access Token として利用している場合は、Access Token 発行時に知らされた有効期限をクライアント側で保持しておくことになると思います。

# どのように API Call するか

素直に [OAuth 2.0 の仕様](http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-7)に従って、Authorization Header をつけましょう。

便利なので Bearer token type しか使ったことが無いですが、 mac token type の方が、より安全っぽいです。

# まとめ
いろいろ調べてる中で、OAuth 2.0 仕様の Editor である Dick Hardt が以下のように述べているのが印象的でした。

> OAuth 2.0 is commonly used by a mobile app to obtain an access token that is then used for subsequent API calls by the mobile app. Sorry the RFC does not make that as clear as it should.

[Best practices for token-based authentication in REST API
](https://groups.google.com/forum/m/#!topic/nodejs/2zCXZ10jFbg)

OAuth 2.0 というと、外部の IdP との連携だけに利用すると思われがちですが、普通の Mobile App - Server 間の認証にも使えるんですよね。