はじめに
こちらの記事は、OAuth2.0 や OIDC という名前をよく聞くが、実体がよくわからない、という人向けです。
業務の中でOAuth2.0を使ったシステムとの連携や OAuth2.0/OIDC のサーバーである keycloakを扱う機会が多く、そこで必要となる用語や概念を整理してまとめておきたいと思ったのが記事作成の動機であります。
背景とおさらい
背景については他のより詳しい記事に譲りたいと思いますが、以下のような必要性から OAuth2.0 の仕様が策定されてきました。
- APIによりWebサービスが公開され、それらサービス同士が連携することが一般的になった。APIを認可する仕組みが必要とされた。
- Webアプリ以外にデスクトップやモバイルアプリに対してもサービス連携できる手段が必要になった。
以下に認証と認可の違いについてまとめておきます。 OAuthは認可のためのプロトコルであり、認証ではないことがポイントです。
認証 (Authentication) はユーザーが本人であることを判断する
- 知識 (what you know) – パスワード、PINなど
- 所持 (what you have) – Authenticatorアプリ、OTP生成器、メールアカウントなど
- 生体 (what you are) – 指紋、虹彩、顔
- MFA – 上記のうち2種類以上
認可(Authorization) はユーザーに機能の使用権限を与える。例えば、SUICAは持参人方式なので、持っている人であれば誰でも入れる。オフィスカードキーも(もちろん貸し借りはいけません)。
OAuthとは何か
OAuthとは権限の認可を行うためのオープンスタンダードです。(OAuthは認証プロトコルではない。)
複数のWebサービス間で、ユーザーのクレデンシャル(ID/Pass)を共有することなく、サービスを連携させる仕組みです。
一時的な鍵を発行して、それを要求した人に安全に渡すためのプロトコルです。
ここで以下のような疑問が出てくるでしょう。以降説明していきます。
- どのようにクレデンシャルを共有することなく連携できるのか?
- 不正なアクセスをどのように防ぐのか?
- OAuthフローを構成する要素と、それぞれの責任はなにか。
OAuthの構成要素
日常生活でありそうなシナリオを想像してください。
- あなたは、インターネットバンキングに口座を持っています。
- あなたは、サードパーティの家計簿アプリ(Webアプリ)を使っています。
- 家計簿アプリは、インターネットバンキングのサービスに接続し、入出金履歴を買い物履歴として取り込む機能があります。
- 家計簿アプリを開いて、口座接続をしようとすると、インターネットバンキングのログイン画面が出ました。。。
このシナリオはOAuthにおいては、以下の4つのアクターが登場します。
OAuthにおける名称 | ![]() ユーザー (リソースオーナー) |
![]() アプリケーション (OAuthクライアント) |
![]() 認可サーバー |
![]() リソースサーバー (APIサーバー) |
---|---|---|---|---|
シナリオにおける例 | 銀行口座の持ち主 | 家計簿アプリ | ログイン画面 | インターネットバンキングサービス |
OAuthフロー
OAuthの基本は、4つのアクターが相互に振る舞い、権限が紐付いたアクセストークンを安全に払い出し、使用することです。
認可サーバー側の設定や、リクエストのパラメータにより認可サーバーの振る舞いが変わり、4種類ありますが、よく利用されるのはそのうち2つです。
-
認可コード方式 (Authorization Code)
- もっともスタンダードで安全な方式
-
クライアント・クレデンシャル方式 (Client Credential)
- バッチ処理など
- インプリシット付与方式 (Implicit Grant)
- リソースオーナー・パスワード・クレデンシャルズ・フロー(Direct Access Grants)
1. 認可コード方式 (Authorization Code)
シナリオに沿って 認可コード方式 を説明します。
前提条件
・ユーザーが認可サーバーに登録済みである
・アプリケーション(OAuthクライアント)が認可サーバーに登録済みである
・リソースサーバーが認可サーバーに登録済みである
-
ユーザーは、アプリケーションの機能を利用します。(メニューを開くなど)
-
アプリケーションは、リソースサーバーのAPIから必要な情報を取りたいですが、権限がないため、認可サーバーを通じてアクセストークンをもらうべく、太字(認可エンドポイント
/auth
)にリダイレクトします。パラメータとして、戻り先のredirect_uri
、client_id
,response_type
を渡します。Type="Authorization Request" Status="302"
Location="https ://oauth.xxbank.com/auth/realms/demo-api/protocol/openid-connect/auth?redirect_uri=https%3A%2F%2Foauth.xxbank.com%2Fgettoken&response_type=code&client_id=demo-client&state=29a1954f-2fdc-4a4f-bf8f-70ed35323b60&nonce=f190d326-f4e7-4e4a-97d6-6831f812a5de&code_challenge_method=S256&code_challenge=OB8AA3k2AvN9u6JwAZVOJfVTlH2FztnDfe75nA76vLk" -
ユーザーは認可サーバーでログインします。
アプリケーションが必要とするスコープについて、アクセス付与することを同意します。認可サーバーは、どのユーザーがどのクライアントにどのスコープのアクセスを与えたかを記録します。
-
リダイレクトで戻します。
戻りのリダイレクトの中で、認可コードが渡されます。https ://accountbook.com/gettoken?state=29a1954f-2fdc-4a4f-bf8f-70ed35323b60&session_state=2a33964f-f1a8-4018-838c-395822cc55d8&code=52bb360a-64c2-42b7-a307-528dd5fe450b.2a33964f-f1a8-4018-838c-395822cc55d8.57d19fab-58cc-4427-ac18-ceb30eddd763
-
アプリケーションにリダイレクトされます。
-
アプリケーションは、トークンエンドポイント(
/token
)に、認可コード、クライアントID、クライアントシークレットを渡します。headers
Content-Type: application/x-www-form-urlencoded"
Authorization :Basic ZGVtby1jbGllbnQ6djZ3dUJQY1RVN1pMdVBGVkZsZXp1VWw4MGp0amJrNTk=
method":"POST"body
code: 52bb360a-64c2-42b7-a307-528dd5fe450b.2a33964f-f1a8-4018-838c-395822cc55d8.57d19fab-58cc-4427-ac18-ceb30eddd763
grant_type: authorization_code
redirect_uri: https ://accountbook.com/gettoken
code_verifier: shJYzJVuyZAPr3rLZ9G6ibZddlvs2e-TvKvJqJ4ZiLQ
url: https ://oauth.xxbank.com/auth/realms/demo-api/protocol/openid-connect/token -
認可サーバーは認可コードを検証します。
-
アクセストークンとリフレッシュトークンを受け取ります。このトークンは
Bearer
となります。{
"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI…",
"expires_in":300,"refresh_expires_in":1800,
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjMDc0N2ExZS1jNmY5LTRmMjUtYTU5Ny05NjQxYTBhZGUzYjMifQ...” ,
"id_token":null,**
"token_type":"Bearer",
"not_before_policy":0,
"session_state":"2a33964f-f1a8-4018-838c-395822cc55d8",
"scope":"profile readdata",
"error":null,
"error_description":null
} -
Bearerトークンをリクエストメッセージに埋め込み、APIを呼び出します。
スコープ
フローの中で スコープ というものが出てきました。
リソースを提供するサービスは、権限の範囲をスコープとして定義することができなす。
定義されたスコープに基づきリソースサーバー側は機能を実装し、また認可サーバーに登録します。 アプリケーションは必要な機能を含むスコープのアクセスを認可サーバーへ要求します。
-
認可サーバーへのリクエストの例
https ://oauth.xxbank.com/auth/realms/demo-api/protocol/openid-connect/auth?redirect_uri=https%3A%2F%2Fhttps://accountbook.com%2Fgettoken&response_type=code&client_id=demo-client&scope=readdata+&state=f8a02b82-bd99-4229-852f-ca53486adbef&nonce=384d69dd-c7c6-48e2-a9be-1d93154b75d6&code_challenge_method=S256&code_challenge=bYl5dqWEe6rQcZnL1eh0gTRfPTWTsXfA95VMehha4aQ
-
認可されたスコープの例
{
"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI…",
"expires_in":300,"refresh_expires_in":1800,
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjMDc0N2ExZS1jNmY5LTRmMjUtYTU5Ny05NjQxYTBhZGUzYjMifQ...” ,
"id_token":null,**
"token_type":"Bearer",
"not_before_policy":0,
"session_state":"2a33964f-f1a8-4018-838c-395822cc55d8",
"scope":"profile readdata",
"error":null,
"error_description":null
}
カードキーに例えると、カードキーにより入れる部屋は決まっているはずです。サーバールームに入るには特別な権限(スコープ)が必要になるでしょう。SUICAに例えると、SUICAの種類によって料金が異なったり、定期の区間が決まっていることでしょう。
アクセストークンの検証
アクセストークンをもってリソースサーバーにリクエストすると、リソースサーバーは以下のいずれかでアクセストークンを検証します。
- 認可サーバーのToken Introspectionのエンドポイントに問い合わせる (
/introspect
) - アクセストークン自体に埋め込まれた認可の範囲の情報を読み取る。(JWT)
リフレッシュトークン
アクセストークンの有効期限は短く(通常は300秒程度に設定する)、期限が切れると再度発行が必要です。アクセストークンとともに発行される リフレッシュトークン を使いアクセストークンを再取得することで、以下のメリットがあります。
- ユーザーが再度クレデンシャルを入力する必要がない
- アプリケーション(OAuthクライアント)と認可サーバー間のみの通信のため、漏洩のリスクが極めて低い
リフレッシュトークンは実質無制限で、オフラインで保存させることもできます。
クライアントの種類
クライアントを整理します。
クライアントの種類により、実装できるフローが異なります。
機密クライアント (Confidential Client) - クレデンシャルの機密性を維持できるクライアント
- サーバーサイドWebアプリケーション
- モバイルアプリなど
公開クライアント (Public Client) - クレデンシャルの機密性を維持できないクライアント
- ブラウザ上のJavascriptアプリケーション
- デスクトップアプリケーション
シナリオの例は機密クライアントであるサーバーサイドWebアプリであり、認可コードフローが採用されています。
2. クライアント・クレデンシャルズ・フロー (Client Credentials)
シナリオに沿って クライアント・クレデンシャルズ・フロー方式 を説明します。
バッチ処理などに使ういわゆるサービスアカウント用のフローであり、ユーザーは関与しません。
3. インプリシット付与方式 (Implicit Grant)
javascriptアプリなど、完全にWebクライアントだけで動作するアプリの場合、クライアントシークレットを隠す意味がないために用いられます。セキュリティ上問題があるため、追加の保護対策が必要になります。
追加の保護対策として以下のいずれかを検討する必要があります。
- サーバーサイドで実行する部分を作り、そこにクライアントシークレットを隠す。つまり認可コード形式を検討する。
- PKCEを使う
4. リソースオーナー・パスワード・クレデンシャルズフロー(Direct Access Grants)
クライアント情報を用いない方式。クレデンシャルをアプリケーションに渡す。セキュリティ上問題あるため通常は用いません。(そもそものOAuthを使う目的がまったくなくなっている)。
OAuthのセキュリティ確保
攻撃者から保護するための仕組みがあり、これらは認可サーバーの設定項目であったり、実装のベストプラクティスになっています。
- stateパラメータ
- PKCE (Proof Key for Code Exchange)
- redirect_uri
- 動的クライアント登録
- Etc.
stateパラメータ
攻撃者は不正な認可コードを挿入してくる可能性があります。
攻撃に対しては、認可リクエストと認可コードの関連付けをすることで不正な認可コードをブロックすることが有効です。
A. state
の生成と保持 - アプリケーションはリダイレクトのURLにstate
を含めます。
https ://oauth.xxbank.com/auth/realms/demo-api/protocol/openid-connect/auth?redirect_uri=https%3A%2F%2Faccountbook.com%2Fgettoken&response_type=code&client_id=demo-client&state=29a1954f-2fdc-4a4f-bf8f-70ed35323b60&nonce=f190d326-f4e7-4e4a-97d6-6831f812a5de&code_challenge_method=S256&code_challenge=OB8AA3k2AvN9u6JwAZVOJfVTlH2FztnDfe75nA76vLk
B. 認可サーバーは同じstate
を返す。認可サーバーの仕様でstate
をechoすることになっています。
https ://accountbook.com/gettoken?state=29a1954f-2fdc-4a4f-bf8f-70ed35323b60&session_state=2a33964f-f1a8-4018-838c-395822cc55d8&code=52bb360a-64c2-42b7-a307-528dd5fe450b.2a33964f-f1a8-4018-838c-395822cc55d8.57d19fab-58cc-4427-ac18-ceb30eddd763
C. アプリケーションはstate
をチェックし、一致すれば認可コードは正しいと判断します。
PKCE (Proof Key for Code Exchange)
攻撃者に認可コードが横取りされると、アクセストークンも取られてしまいます。
認可リクエストとトークンリクエストの関連付けをすることで、認可コードが攻撃者に横取りされた時に保護できます。認可コード方式を使っている公開クライアントで用いると有効です。
A. アプリケーションはcode_verifier
の生成と保持、code_challenge
を計算して、認可サーバーに渡します。
https ://oauth.xxbank.com/auth/realms/demo-api/protocol/openid-connect/auth?redirect_uri=https%3A%2F%2Faccountbook.com%2Fgettoken&response_type=code&client_id=demo-client&state=29a1954f-2fdc-4a4f-bf8f-70ed35323b60&nonce=f190d326-f4e7-4e4a-97d6-6831f812a5de&code_challenge_method=S256&code_challenge=OB8AA3k2AvN9u6JwAZVOJfVTlH2FztnDfe75nA76vLk
B. 認可サーバーは認可コードとcode_challenge
の紐付けをします。
C. 認可サーバーは認可コードとcode_verifier
を検証します。
headers
Content-Type: application/x-www-form-urlencoded"
Authorization :Basic ZGVtby1jbGllbnQ6djZ3dUJQY1RVN1pMdVBGVkZsZXp1VWw4MGp0amJrNTk=
method":"POST"
body
code: 52bb360a-64c2-42b7-a307-528dd5fe450b.2a33964f-f1a8-4018-838c-395822cc55d8.57d19fab-58cc-4427-ac18-ceb30eddd763
grant_type: authorization_code
redirect_uri: https ://accountbook.com/gettoken
code_verifier: shJYzJVuyZAPr3rLZ9G6ibZddlvs2e-TvKvJqJ4ZiLQ
url: https ://oauth.xxbank.com/auth/realms/demo-api/protocol/openid-connect/token
redirect_uri
redirect_uri
は認可サーバーでチェックされており、不正なredirect先に認可コードを送ることはできないようになっています。
https ://oauth.xxbank.com/auth/realms/demo-api/protocol/openid-connect/auth?redirect_uri=https%3A%2F%2Faccountbook.com%2Fgettoken&response_type=code&client_id=demo-client&state=29a1954f-2fdc-4a4f-bf8f-70ed35323b60&nonce=f190d326-f4e7-4e4a-97d6-6831f812a5de&code_challenge_method=S256&code_challenge=OB8AA3k2AvN9u6JwAZVOJfVTlH2FztnDfe75nA76vLk
緩すぎるredirect_uri
を設定しておくと、意図しないredirect先が許容され、そこで認可コードが横取りされる可能性があります。そのため、できるだけ詳細なパスを設定するべきです。
case 1) : 許可されるredirect_uri : https://example.com/path/*
redirect_uri=https://example.com/path/subdir -> OK
redirect_uri=https://example.com/anohter/subdir -> NG
case 2) 許可されるredirect_uri : /*
redirect_uri=https://example.com/path/subdir -> OK
redirect_uri=https://example.com/anohter/subdir -> OK
redirect_uri=https://attacker.com/ -> OK (NGとなるべき)
動的クライアント登録 (Dynamic Client Registration)
クライアントIDとクライアントシークレットを動的に生成する。モバイルアプリで初回起動時など、これを行うことで、パーソナルな範囲で認可を行うことができます。
- クライアント登録エンドポイント(
/register
)
OAuth2.0の拡張
一般的に利用されているOAuth2.0の拡張を説明します。
JWT形式のアクセストークン
OAuthのアクセストークンは文字列であることが規定されていますが、JWT形式が一般的に採用されています。
- Handle型 – アクセストークンは意味のないランダムな文字列
- Assertion型 – アクセストークンは意味を持つ文字列。JWT (JSON Web Token)など。
JWTに含む項目は、様々なアプリケーションで使えるよう事前に定義されています。事前定義項目以外にも自由に追加することができます。また、事前定義項目は必須項目ではありません。
- iss - 発行者(認可サーバーURI)
- sub - 対象者、つまりリソースの所有者
- aud - 受け手、つまり誰が受け取るべきか
- exp - 有効期限
- nbf - 有効開始時
- iat - 発行時
- jti - トークンの識別子
必要な情報はトークンに入っているので、認可サーバーに問い合わせなくともリソースサーバー側で検証することができます。JWT署名を検証するために、認可サーバーのエンドポイント(/certs
)から鍵を取得します。
トークンの無効化
アプリケーションは有効期限前にアクセストークン、リフレッシュトークンを無効化することができます。そのために、/revoke
エンドポイントにリクエストを出します。
OIDC (Open ID Connect)
OAuthの拡張の1つであり、OAuthプロトコル上に、認証の機能を実装し、フェデレーション(異なるドメイン間や異なるサービス間でSSOを実現すること)を実現します。
OIDCにおいてはアクターの名称が変わります。
OAuth | ![]() ユーザー (リソースオーナー) |
![]() アプリケーション (OAuthクライアント) |
![]() 認可サーバー |
![]() リソースサーバー (ユーザー情報) |
---|---|---|---|---|
OIDC | ![]() エンドユーザー |
![]() RP (Relying Party) |
![]() アイデンティティ・プロバイダ (IdP) |
- スコープに
openid
が定義されます。API呼び出し時にopenid
をスコープに要求すると、IDトークンが追加で返されます。 - IDトークンを使ってOPのUserInfoエンドポイント(
/userinfo
)にユーザー情報を問い合わせすることができます。 -
redirect_uri
が必須となります。
OIDCのセキュリティ確保
-
nonce
パラメータを使って認可リクエストとIDトークンの関連付けすることで、攻撃者が認可コードを挿入して、誤ったIDトークンを取得させるのを防ぎます。
UMA (User Managed Access)
OAuthに基づくアクセス管理のプロトコルで、ユーザーが自ら保有するリソースの公開範囲(URI、公開先のユーザーなど)を制御します。
相手のユーザーに公開を要求したり、承諾/却下などを行うなど、SharepointやBOXの共有のフローをイメージするとわかりやすいでしょう。
まとめ
OAuthとは権限の認可を行うためのオープンスタンダードです。
OIDCはOAuth2.0の拡張で、 認証 を行います。
OAuth2.0とOIDCは同時に現れることも多く、わかりづらい一因になっているかと思います。
この記事が参考になれば幸いです。