認可を行うプロトコルであるOAuth2.0とは何でしょうか?OIDCとの違いは?
実は、皆さんがログインで知らずに使っている身近なものなのです!
認可サーバーによってユーザー検証をすることで、クライアント(サードパーティーアプリ)にHTTPサービスのユーザー名、パスワードを教える必要がなくなります。そのうえで、そのアプリへのHTTPアクセスが部分的に可能になります。
分かりにくいと思うので、プロトコルの仕組みを詳しく見ていきましょう。
今回は、Auth屋(https://authya.booth.pm/)のシリーズを参考にまとめてみました。
認証と認可とは
ログインでは基本的に認証(Authentication=AuthN)と認可(Authorization=AuthZ)が行われます。
認証はユーザーがなりすましではなく、ユーザーであることを確認します。
認可は確認できたユーザーに対して、リソース(個人情報など)へのアクセス権限を付与します。
OIDCとOAuth2.0の違い
OIDC(OpenID Connect)はOAuth2.0の拡張仕様となっています。
OAuth2.0では権限委譲、つまり認可(Authorization=AuthZ)が行われます。ただ、これだけでは認証プロセスがなく、脆弱性があるのです。。!OAuth2.0で認可が行われた後に、発行されたアクセストークンでプロフィール取得のAPIにアクセスし、認証が初めて行われます。ここでログインが完了します。
逆に、OIDCではこれだけで認証と認可が完了します。IDプロバイダとの対話のみでログインが完了し、サードパーティーでユーザー情報を管理しなくてよくなるため、よりセキュアというわけです。
そして、以下の方式が成り立ちます。
OIDC = OAuth + ID トークン + UserInfo エンドポイント
IDトークンは認証のために使われます。サードパーティーのプロフィールAPI(リソースサーバー)の代わりにUserInfoエンドポイントがOIDCプロトコルに含まれており、認可サーバー(IDプロバイダ)として提供されるイメージです。
一般的な認可コードフローを比較してみましょう。

OIDC

OAuth2.0

OIDCではトークンエンドポイントでIDトークンが返却され、クライアントでIDトークンの検証が行われることが分かります。
また、上図のようにロール名やフロー名も若干違います。OIDCは認可コードフローと呼び、OAuth2.0は認可コードグラントと呼びます。グラントには様々な種類があり、以下のような対応になります。

ちなみにAzure公式ドキュメントには以下のようにあります。
OAuth2.0

OIDC

よく見るとOIDCの⑤User consents to permissionsがOAuth2.0にはないことが分かります。Microsoft Entra ID(IDプロバイダー)はOIDCでは認証と認可を実行し、OAuth2.0では認可を実行(トークンを発行)します。
OAuth2.0のグラントの種類
OAuth2.0には主に以下グラントの種類があります。
- 認可コードグラント
- インプリシットグラント
- クライアントクリデンシャルグラント
- ROPCグラント
- ハイブリットグラント
認証リクエストパラメータにresponse_typeを設定できるのですが、設定値によって以下のようにフローが変わってきます。


response_typeにcodeを付けてリクエストを送ると認可コードを返却してくれるようになってるね。
この認可コードとは一体なんでしょうか?それぞれの種類のグラントを見ながら抑えていきましょう。
認可コードグラント
認可コードグラントでは認可コードを認可サーバーが取得します。この認可コードによりこのグラントが一番安全で、一般的に使用されています。
以下のようにPKCE(Proof Key for Code Exchange)を付けるのが推奨されており、よりセキュアになります。PKCE付きのフローである、アプリからのリクエストでcode_challengeとcode_verifierによって同一ユーザーであることを検証しています。

認可リクエストでcode_challengeを送り、トークンリクエストでそれに対応したcode_verifierを送る。認可サーバーはそれぞれの値を検証して同一人物か確認できるというわけだ。
code_challenge_methodで暗号方式をリクエストします。例として以下の方式になります。
code_challenge_method:
- plain:
code_challenge
=code_verifier
- S256:
code_challenge
= BASE64URL-ENCODE(SHA256(ASCII(code_verifier
)))
OAuth2.0 ★PKCE付き

OAuth2.0

2-4
③認可リクエスト
GET /authorize
?response_type=code
&client_id=s6BhdRkqt3
&state=xyz
&scope=read
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcallback
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM //追加 認可サーバーが保存(トークンリクエストの検証に利用)
&code_challenge_method=S256 //追加 認可サーバーが保存(トークンリクエストの検証に利用)
HTTP/1.1
Host: auth.example.com
9-10
URIのプロトコル部分はカスタムスキームmyapp://になっている。
HTTP/1.1 302 Found
Location: myapp://client.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
11-13
⑪トークンリクエスト
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcallback
&client_id=s6BhdRkqt3
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk //追加
※悪意あるアプリはcode_verifierを知らない!!(もしcode_challengeが流出してもS256を使えばcode_verifierの値はわからない)
⑫認可サーバーでcode_verifierからcode_challenge_methodでcode_challengeを算出(④と一致するか)
ちなみにOIDCの認可コードフローは以下になります。
右図のOIDC+OAuth2.0はIDプロバイダと認可サーバが一緒となっており、認証リクエストのscopeというリクエストパラメータでopenidとGoogle Photoのscopeを指定すれば、一度にuserinfoとGoogle Photo APIへのアクセス権限のあるアクセストークンを取得することができます。

例えば、ログイン完了後にGoogle Photoからは画像を取得したい!って時とかに有効だね。
OIDC

OIDC+OAuth2.0

インプリシットグラント
パブリッククライアントのためのものですが、非推奨になります。
認可コードグラントと比べても分かるように、認可コードを取得する必要がなく、直接アクセストークンを認可リクエストのみで取得できてしまっています。
インプリシットグラント

認可コードグラント

クライアントクリデンシャルとROPCグラント
クライアントクリデンシャルは2-legged OAuth:2社間のやり取りの際に使用します。
ROPCはオーナーのユーザー名とパスワードがクライアントを通して認可サーバーに送られる。認可サーバーとクライアントの提供元が同じ組織である場合に使用します。
クライアントクリデンシャルグラント

ROPCグラント

トークンの種類
トークンとは認可サーバーがクライアントに発行するものですが、種類があります。種類によって、そのトークンの使える矛先や期限が変わってきます。
- 認可コード<オーナーの合意>:クライアント→認可サーバー(token endpoint)
- クライアントがtoken endpointにアクセストークンを要求する際に使用。
- 取得方法:オーナー→認可サーバー(認可エンドポイント)
ユーザー名とパスワード、2段階認証、顔認証、指紋認証などなど - 複数回使えない。1度きり!
- 有効期限:数分ほど。10分以内推奨。
- アクセストークン:クライアント→リソースサーバー
- リソースサーバー(google等)へのアクセスで必要。(許可してOKか判断。オーナーがOKしたか)
- スコープ:読み込み権限、書き込み権限とか
- 有効期限:普通はリフレッシュトークンより短い。数分〜1時間
- リフレッシュトークン:クライアント→認可サーバー
- 必須でない。アクセストークン再発行を認可サーバーに要求する。
- 有効期限:数日〜数週間
IDトークンの形式
IDトークンはOIDCの認証プロセスで使用されると話しましたが、JWT(Json Web Token)という以下の形式で表されます。Base64URL方式でエンコードされていますが、デコードしてしまえば簡単に中身のJSONを確認できます。
JWT(Json Web Token) ={ { header.payloadをencode(Base64URL方式) }.signature }をencode(Base64URL方式)
以下は例になります。
header
{
"typ": "JWT",
"alg": "RS256",
"kid": "84f294c11100000d079fee68138f52133d3e228c" //IDプロバイダの公開鍵Id
}
payload
{
"iss": "https://accounts.google.com", //ISSuer
"aud": "6727653....ibqog5.apps.googleusercontent.com", //AUDience
"sub": "100007684956724211111", //SUBject
"iat": 1563792930, //Issued AT
"exp": 1563796530, //EXPiration time
"nonce": "0394852-3190485-2490358" //リプレイアタック防止 認証リクエストのnonceと同じ値
}
Azure AD B2Cで認証してみた
実際にAzure AD B2Cで認証フローを実行してみます。
以下のドキュメントでユーザーフローまたはカスタムポリシーがテナント上に作成済みの前提です。
Tutorial – Create user flows and custom policies – Azure Active Directory B2C | Microsoft Learn
認可コードフロー
クライアントの作成
まずはAzure AD B2Cのテナント上で使うアプリケーションをテナントに以下の2つを登録します。
以下のようなイメージです。(参照:Authentication protocols in Azure Active Directory B2C | Microsoft Learn)
- Web App:認可サーバーのクライアント
- API App:リソースへのアクセス権限(アクセストークン)のスコープを管理

では、以下ドキュメントを参照して作成していきます。
Tutorial – Register a web application in Azure Active Directory B2C – Azure AD B2C | Microsoft Learn
API APP
アクセス権にはoffline_accessとopenidを委任済みで設定します。また、APIの公開でスコープを指定します。これは、認可リクエストで指定する値になります。(例:https://contoso.onmicrosoft.com/api
)

Web App
offline_accessとopenidの他に、先程作成したAPI APPアプリケーションへのアクセス権を付与します。

認証と認可完了後のリダイレクト先を設定します。本来であればアプリケーションが起動しているエンドポイント先を指定しますが、テストの為、JWT画面(https://jwt.ms)に設定します。

認可コードフローリクエストの実行
以下のリクエストを試してみます。
- Auth GET:認証リクエストを送信します。
- Token POST:トークンリクエストを送信します。
- Userinfo GET:ユーザー情報を取得します。
- OIDC Refresh Token POST:リフレッシュトークンリクエストを送信します。
Auth GET
Webブラウザで以下URLを送信します。
client_idには先程作成したWeb AppのクライアントID、scopeはAPI Appのスコープ値を設定します。
https://{{tenant_name}}.b2clogin.com/{{tenant_name}}.onmicrosoft.com/{{policy_name}}/oauth2/v2.0/authorize
?client_id={{client_id}}
&response_type=code id_token
&redirect_uri=https://jwt.ms
&response_mode=query
&tenant={{tenant_id}}
&scope=openid {{scope}} offline_access
&prompt=login
&code_challenge=YTFjNjI1OWYzMzA3MTI4ZDY2Njg5M2RkNmVjNDE5YmEyZGRhOGYyM2IzNjdmZWFhMTQ1ODg3NDcxY2Nl
すると、Azure B2Cの認証画面が開くので、ログインをします。
認証に成功すると、URL上のクエリパラメータにて認可コードが返却されます。(https:~?code=XXXのように)
Token POST
トークンリクエストを送信します。
こちらはPOSTなのでPostmanやCurlなどを使用して送信します。ここで、codeのパラメータに先程返却された認可コードを設定します。また、client_secretはWeb Appのシークレット値です。
https://{{tenant_name}}.b2clogin.com/{{tenant_name}}.onmicrosoft.com/{{policy_name}}/oauth2/v2.0/token

すると、アクセストークンやIDトークンが返却されます。無事、認証と認可が完了しました。
Userinfo GET
実際に取得したアクセストークンでユーザー情報にアクセスできるか確認してみます。
https://{{tenant_name}}.b2clogin.com/{{tenant_name}}.onmicrosoft.com/{{policy_name}}/openid/v2.0/userinfo

ユーザー情報が返却されれば成功です。
OIDC Refresh Token POST
次にリフレッシュトークンをリクエストしてみましょう。ここで、refresh_tokenにトークンリクエストで返却されたrefresh tokenの値を設定します。
https://{{tenant_name}}.b2clogin.com/{{tenant_name}}.onmicrosoft.com/B2C_1A_SIGNIN/oauth2/v2.0/token

すると、リフレッシュトークンと共に、新しいアクセストークンやIDトークンが返却されます。
ROPCフロー
ROPCフローでも試してみます。
クライアントの作成
認可コードフローと同様に、以下ドキュメントからROPCで認証する為のアプリケーションを作成します。
Set up a resource owner password credentials flow – Azure AD B2C | Microsoft Learn
“oauth2AllowImplicitFlow
“をtrueに設定するなど、先程の認可コードフローよりもセキュリティが弱いことが分かります。
ROPCフローリクエストの送信
ROPC POST
ROPCリクエストを送信します。
https://{{tenant_name}}.b2clogin.com/{{tenant_name}}.onmicrosoft.com/{{ropc_policy_name}}/oauth2/v2.0/token

このように、先程に作成したアプリのクライアントIDが分かってしまえば、シークレットなしでログインできてしまいます。
ROPC Refresh Token POST
https://{{tenant_name}}.b2clogin.com/{{tenant_name}}.onmicrosoft.com/B2C_1A_ROPC_AUTH/oauth2/v2.0/token
?grant_type=refresh_token
&response_type=id_token
&client_id={{ropc_client_id}}
&resource={{ropc_client_id}}&refresh_token=eyJra.....
最後に
いかがでしたでしょうか。
完璧とまでいかずとも、少しでも認証のプロトコルの理解の手助けになれたら嬉しいです。
もっと理解するためにも、今回ふんだんに使わせてもらったAuth屋さん(https://authya.booth.pm/)のシリーズをお勧めします(^^)/