プロダクト開発

ユーザー認証とは。Web、APIでの認証の仕組みと認証方法

ユーザー認証を実装することになったんだけど、どう設計すればいいの? 認証にはいろんなやり方があると思うんだけど、なにから考えればいいのか分からない。

といった悩みに、記事でお答えしていきます。

この記事では、Webアプリケーションやネイティブアプリでのユーザー認証の仕組みや方法を説明した上で、どの方法がいいのかをケースごとに見ていきます。

私はBtoB、BtoCのWebアプリケーションやモバイルアプリをいくつか作ってきました。その度に、ソフトウェアの種類に応じたユーザー認証について考え、設計・実装してきました。その経験をもとにこの記事を書いています。

この記事の対象となる読者の方

この記事はプログラミングを学習中の方、エンジニアとしての実務経験が1〜3年目の方を対象として書いています。わかりやすさを優先して、用語の使い方が厳密でないところがあります。その際は注釈として補足しています。

著者
ぜに/Hiroki Zenigami

Webエンジニア&プロダクトマネージャ←プログラミング教育で起業←東大院←熊本高専。 共著に「現場で使えるRuby on Rails 5」。

目次

ユーザー認証とは

この記事でいうユーザー認証とは、Webアプリケーションやネイティブアプリにおいてリクエストした相手がユーザーであることを確認することをいいます。たとえば現実世界でいう運転免許証での本人確認ですね。

ユーザーにだけなんらかの機能を提供したいときに、ユーザー認証を行います。

ユーザー認証のフロー

ユーザー認証は、基本的に次のようなフローで行われます:

  1. クライアントからサーバーに認証情報を送る
  2. サーバー上で認証情報を検証する
  3. 認証情報が正しければ、クライアントにアクセストークンを送る(+必要に応じてセッションに記録する)

たとえばフォームにメールアドレスとパスワードを入力・送信し、入力内容が正しければサーバーからアクセストークンが渡される、というイメージですね。

ユーザー認証を設計する上で決めるべきこと

このユーザー認証ですが、設計する上で次の4つについて決める必要があります:

順番項目選択肢
1認証方法をどうするかパスワード認証、OpenID Connect
2アクセストークンをどう管理するかJWT
3アクセストークンをどう引き回すかAuthorizationヘッダー、Cookieヘッダー
4アクセストークンをどう保持するかOS標準のストア、メモリ、Cookie、localStorage

今はまだそれぞれがなにかを理解する必要はありません。これからひとつずつ見ていきましょう。

1. ユーザー認証の方法

まず、ユーザー認証の方法には、大きく次の二つがあります:

番号認証方法概要
1パスワード認証IDとパスワードをサーバーに送る
2OpenID Connect外部のプロバイダ上で認証し、アクセストークンをサーバーに送る

OpenID Connectは、OAuth認証と聞くとなじみがあると思います。たとえばGoogleやTwitterなどのアカウントによる認証ですね。ただ、『OAuth認証』という言葉には語弊があります。これは後述しますね。

この二つの認証方法についてひとつずつ見ていきます。

認証方法(1): パスワード認証

パスワード認証にも、大きく二つの認証方法があります:

  1. Basic認証やDigest認証など
  2. 独自の認証機構

独自の認証機構とは、たとえばフォームからIDとパスワードをサーバーに送って、ユーザー基盤をもとに認証を行う一般的なやり方ですね。広く使われているフルスタックのWebフレームワークならだいたい認証機構をもっていると思います。

この認証方法はパスワードを平文で送ることになります。仮にSSLで通信を暗号化していたとしても、ログに書かれて流出につながったりします。

認証方法(2): OpenID Connect

次にOpenID Connectについて説明します。これはGoogleやTwitterなどのアカウントで認証する方法ですね。

まず、前提知識として、OAuthという『アクセストークンを発行する仕組み』があります。アクセストークンは、アプリケーションをAPI経由で操作するときなんかに使われますね。たとえばTwitterクライアントを自作するときにアクセストークンを使ったりします。

このOAuthは前述のとおりアクセストークンを発行する仕組みであって、認証については定められていません。認証に必要な、ユーザー情報などの取得については決まっていないんですね。

このOAuthを拡張し、ユーザー情報の取得についてなどを標準化したのがOpenID Connectというわけです。これについては『OpenID Connectユースケース、OAuth 2.0の違い・共通点まとめ』の説明がわかりやすいです:

OpenID Connectは、『OAuth 2.0を使ってID連携をする際に、OAuth 2.0では標準化されていない機能で、かつID連携には共通して必要となる機能を標準化した』OAuth 2.0の拡張仕様の一つである。

OpenID Connectによる認証のフロー

このOpenID Connectは広く使われている認証方法なので、フローを押さえておきます。まず、IDトークンという『ユーザー情報が含まれたアクセストークン』があり、これを次のフローでやりとりします。プロバイダというのはGoogleなどの認証を行うサービスを想定しています:

順番リクエスト元相手内容
1クライアントプロバイダIDトークンを要求する
2プロバイダユーザーIDトークンの発行可否を聞く。あわせて認証を行う
3ユーザープロバイダ発行可否と、発行する場合に認証情報を送る
4プロバイダクライアントIDトークンを発行する

この1と4のやりとりを標準化したのがOpenID Connectです。OAuthが『アクセストークンを発行する仕組み』なのに対して、OpenID Connectはこれを拡張して『IDトークンを発行する仕組み』と考えるとわかりやすいかもしれません。

詳しいことはさておき、『Googleなどのアカウント認証を使う』=『OpenID Connectを使う』という認識がもてればいいのかな、と思います。

Googleの認証方法はOpenID Connectではない

OpenID Connectについて書きましたが、実際のところGoogleやTwitterの認証はOpenID Connectではありません。GoogleはOpenID Connectの仕様に準拠しつつ、OAuth 2.0を採用しています。TwitterはOAuth 1.0aという仕様で、OpenID Connectには準拠していません。

いずれにしてもOAuthをベースにした認証を行なっていますが、ここではわかりやすさのためにOpenID Connectという用語に統一して説明しています。

2. JWTでトークンを管理する

OpenID ConnectはIDトークンをやり取りする、と書きました。これはJWTというトークンの形式になっています。JWTはユーザー認証において大切な概念なので、簡単に説明しておきますね。

JWTとは

JWTは『二者間で情報のやりとりを目的とした、JSONベースの形式について規定した標準仕様』です。『ジョット』と読みます。たとえばJWTは次のような値になります:

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

わかりづらいですが、三つの文字列がピリオドで連結されています。わかりやすく書くと:

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

となります。それぞれBase64でエンコードされた文字列となっています。上から順番に、次のような名前と役割があります:

順番名前役割
1ヘッダー署名の検証に必要な情報
2ペイロードやり取りに必要な情報。ユーザー情報など
3署名検証する内容

署名には秘密鍵を使うため、これを用いて検証を行うことができます。署名はヘッダーやペイロードをもとに行うので、内容の改ざんができない、という仕組みです。

クライアントからトークンを受け取ると、サーバー側でトークンが正しいかどうかをその場で検証できます。JWTを用いることで、パスワードなどの認証情報をデータベースに保存する必要がない、というメリットがあります。

正しくはJWS

上の説明ではJWTという用語を出しました。これは広く使われているのでこうしましたが、正しくはJWSという仕様です。JWSはJSON Web Signatureで、署名つきのJWTの場合にとりわけてこう呼びます。

JWTは使うな?

JWTは危険だという議論があります。確かにJWTの署名を検証する方法によっては脆弱性を作り出してしまうという問題があります。たとえばヘッダーの情報を改ざんするやり方です。これについては『JSON Web Token(JWT)の紹介とYahoo! JAPANにおけるJWTの活用』に詳しいです。

ただ、これは実装によって対応できる問題であり、ほとんどのライブラリ側で対応されています。JWTは使うメリットが大きいので、脆弱性への対応がされていることを確認した上で、積極的に使うべきだと思っています。

3. アクセストークンをどう引き回すか

上で、ユーザー認証の方法としてパスワードとOpenID Connectについて書きました。この認証結果として、アクセストークンが返されることになります。以降は、このアクセストークンを用いて認証することになります。

では、このアクセストークンはどう引き回せばいいでしょうか。つまり、どうやってクライアントからサーバーにリクエストを送ればいいのでしょうか。これには次の二つがありますが、結論からいうと『特定の条件を除いてAuthorizationヘッダーで行えばよい』と思っています。

方法1: Authorizationヘッダー

このやり方は、認証を行うために定義されているAuthorizationヘッダーにアクセストークンを入れるやり方です。たとえば次のような形式になります:

Authorization: Bearer <アクセストークン>

このAuthorizationヘッダーは、Basic認証やDigest認証で使われていました。その後RFC6750でBearerというスキームが策定されました。これは単一の文字列を認証情報として送信するのに適しています

特徴をまとめると:

  • ステートレス。サーバー側にセッションストアがいらない
  • ネイティブプラットフォームで扱いやすい
  • Cookieが使用できない環境でも問題ない

Authorizationヘッダーを用いるやり方は、OpenID Connect、パスワードのどちらの認証方法にも適しています。

方法2: Cookieヘッダー

このやり方は、CookieヘッダーにセッションIDを入れつつ、サーバー上でも保存しておくやり方です。ユーザー認証を一度行ったら、以降はCookieヘッダーに含まれるセッションIDとサーバー側のセッションIDを照合してユーザーを識別します。

次のような形でサーバー側からクライアントにCookieのセットをリクエストして:

Set-Cookie: SID=<セッションID>

次のような形でクライアントからサーバーにリクエストを行います:

Cookie: SID=<セッションID>

特徴をまとめると:

  • ステートフル。データベースなど外部のストレージからセッションを取得する必要がある
  • サーバーにリクエストするたびに自動でCookieが送信される
  • CSRF脆弱性への対策をする必要がある
  • 異なるドメインに対して制約がつく(=CORS)

APIは一般的にステートレスで行うため、Cookieヘッダーによる引き回しは実用的ではないといえます。このやり方はパスワード認証かつ、ネイティブアプリやSPAでない従来のWebアプリケーションに適していると思います。

4. アクセストークンをどう保持するか

長くなりましたが、最後のテーマです。前述のとおり、パスワードやOpenID Connectでユーザー認証を行うと、アクセストークンが発行されます。これをヘッダーに乗せて認証します。つまり、クライアント側でアクセストークンを保持しておかなければなりません。

アクセストークンはどう保存すればいいのでしょうか。大きく次の4つがありますが、結論からいうと可能な限り『OS標準のストレージ』か『メモリ』に保持します:

番号場所概要
4.1OS標準のストレージiOSのKeyChain、AndroidのKeyStore
4.2メモリJavaScriptの変数など
4.3CookieWebブラウザのCookie
4.4localStorageWeb Storage APIのlocalStorage

ひとつずつ見ていきます。

4.1 OS標準のストレージ

iOSのKeyChainやAndroidのKeyStoreなど、OSが標準で提供しているストレージを利用するやり方です。Auth0によるアクセストークンの保持についての記事でも、次のメモリとあわせて推奨されているやり方です。

4.2 メモリ

JavaScriptの変数などに格納し、CookieやlocalStorageには保存しないやり方です。スコープに気をつける必要はありますが、永続化しないため安全といえます。

ただ、ページから離脱するとアクセストークンが消えてしまうので、ソフトウェアが要件を満たせる場合のみ採用できるやり方になります。

これはWebブラウザのCookieを使うやり方ですね。Cookieを使うやり方にはいくつか問題があります。たとえばXSS脆弱性やCSRF脆弱性などです。

CookieにSecure属性やHttpOnly属性をつければ安全性はいくらか高まります。ただ、Authorizationヘッダーを用いる場合JavaScriptを用いることになるのでHttpOnly属性をつけられません。つまりXSS脆弱性が残ってしまいます。

4.4 localStorage

Web Storage APIのlocalStorageを使うやり方です。これもCookieと同じくJavaScriptから操作可能なので、XSS脆弱性が残ります。また、localStorageには『HTML5のLocal Storageを使ってはいけない』で書かれているようないくつかの問題点もあります。

以上、いずれの場合もクライアントとサーバーのやり取りにHTTPSで通信するのは必須ですね。HTTPだと通信が見えてしまうので、アクセストークンが盗まれる可能性があります。

また、CookieやlocalStorageを使う場合は有効期限を短くしてリスクを下げるなどの対策が必要だと思います。

ユーザー認証の設計例

記事のはじめにも書きましたが、ユーザー認証を設計する上で決めるべきこととして、次の4つがあります:

順番項目選択肢
1認証方法をどうするかパスワード認証、OpenID Connect
2アクセストークンをどう管理するかJWT
3アクセストークンをどう引き回すかAuthorizationヘッダー、Cookieヘッダー
4アクセストークンをどう保持するかOS標準のストア、メモリ、Cookie、localStorage

この4つについて、どう選択すればいいのでしょうか。これはソフトウェアの種類や事業のステージなどによって異なりますが、いくつかの例をとおして見てみます。

ケースに応じたユーザー認証の設計例

たとえばフルスタックなWebフレームワークを用いて、APIを使わないWebアプリケーションを構築するケース。この場合はパスワードで認証し、Cookieヘッダーでリクエストします。JWTは使わず、アクセストークンはクライアントのCookieに保持します。

SPAでサーバーはAPIとしてのみ利用する場合は、OpenID Connectで認証し、Authorizationヘッダーでやり取りします。アクセストークンはメモリ上に保持します。

ネイティブアプリかつユーザー基盤を自前で構築する場合は、パスワード認証をしつつJWTでやり取りします。リクエストはAuthorizationヘッダーを用い、KeyChainやKeyStoreといったOS標準のストレージを利用します。

ユーザー認証のスキルを身につける

ユーザー認証は、いろんな開発の現場にとって重要なスキルです。実際の現場で開発することで、より高いスキルを身につけることができます。ただ、他の現場に出会うには準備が必要になります。

少しでも他の現場が気になるなら、あらかじめ転職サイト・副業サイトに登録しておくといいです。次の記事で、登録しておくべきサイトについて説明しています。

まとめ

長くなってしまいましたが、ユーザー認証を設計するための基本的な知識はだいたい書けたかと思います。

OpenID ConnectやJWTなど、実際に開発する上でもっと深く掘り下げなければならない知識はあると思いますが、この記事がユーザー認証を実装するときの参考になればうれしいです。

著者
ぜに/Hiroki Zenigami

Webエンジニア&プロダクトマネージャ←プログラミング教育で起業←東大院←熊本高専。 共著に「現場で使えるRuby on Rails 5」。

関連記事関連書籍人気記事
applis
エンジニアとしてのんびり暮らす
お問い合わせ
ご意見・ご質問やお仕事のご依頼などは下記よりお願いいたします
お問い合わせ
© applis