OIDCを理解するためにcurlコマンドにてリソースにアクセスしてみた
はじめに
認可と認証周りの学習をしておりましてOAuth2について学習をしました。
その続きとしてOIDCによる認可と認証のハンズオンを進めていました。
今回はAuth屋さんの下記の書籍にて第5章のハンズオンを自分なりにまとめてものになります。
こちらの書籍は非常に丁寧にまとめられているのでこれからOAuthやOIDCについて学習したい方にはおすすめです。
前提条件
- GCPにてOAuth同意画面が構成されていること
- GCPにてOAuthクライアントの登録が完了していること
コマンド
1.認可コードの取得 (ブラウザでアクセス)
下記のURLにブラウザでアクセスします。
※秘密情報については*にて置換しています。
リクエスト
https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=**********************.apps.googleusercontent.com&state=xyz&scope=openid profile&redirect_uri=http://127.0.0.1/callback&nonce=abc
response_type: 認可コードを要求するためのcodeを指定
client_id: gcpにてクライアント登録したときに取得したもの
state: ランダムな文字列。ここでは適当にxyz
scope: OIDCではopenidが必須であり加えてprofileも設定する ※OAuth2の場合は有効化したPhotos Library APIの読み権限を表す文字列などを指定している
redirect_url: http://127.0.0.1 ※これはクライアント登録時にコールバックURLとして登録してます
nonce: ランダムな文字列。ここでは適当にabc ※こちらの記事が参考になります
レスポンス
ブラウザのアドレスバーから下記のURLを取得
http://127.0.0.1/callback?state=xyz&code=**********************&scope=profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=1&prompt=consent
code: ここに認可コードが記載されている
state:認可リクエストで指定したxyz。これをCSRF防止のためにセッションと一緒に管理する
scope: 指定したopenidとprofile、Google独自の表現が追加
2.認可コードを使ってトークンエンドポイントにアクセスしアクセストークンとIDトークンを取得する (ここからcurl)
リクエスト
curl -d "client_id=**********************.apps.googleusercontent.com" -d "client_secret=**********************" -d "redirect_uri=http://127.0.0.1/callback" -d "grant_type=authorization_code" -d "code=**********************" https://oauth2.googleapis.com/token
client_id:gcpにてクライアント登録したときに取得したもの
client_secret:gcpにてクライアント登録したときに取得したもの
redirect_uri: http://127.0.0.1/callback
grant_type: authorization_codeを指定
code: URLエンコードされた認可コードを記載
レスポンス
{
"access_token": "**********************",
"expires_in": 3599,
"scope": "openid https://www.googleapis.com/auth/userinfo.profile",
"token_type": "Bearer",
"id_token": "**********************"
}
access_token: アクセストークン
expires_in: アクセストークンの有効期限(秒)
scope: 有効化したPhotos Library APIの読み権限を表す文字列
token_type: トークンの種類。これはBearerトークン
id_token: 検証のためのIDトークン。これが付くことがOAuth認証(OAuth+プロフィールAPIを用いた認証)との差分
3.IDトークンの検証
※Ubuntu22.04環境で下記のコマンドを実行しています
参考
トークン内の情報を抽出
# 環境変数にトークンを設定
ID_TOKEN=**************************
# トークンから情報を抜き出す。コマンドの詳細は書籍を参照
echo -n $ID_TOKEN | cut -d'.' -f 2 | tr '-' '+' | tr '_' '/' | awk '{ L=length($1)/4; L=int((L==int(L))?L:int(L)+1)*4; printf "%-*s",L, $1}'| tr ' ' '=' | base64 -d | jq .
{
"iss": "https://accounts.google.com",
"azp": "**********************.apps.googleusercontent.com",
"aud": "**********************.apps.googleusercontent.com",
"sub": "**********************",
"at_hash": "**********************",
"nonce": "abc",
"name": "キヨマル",
"picture": "**********************",
"given_name": "マル",
"family_name": "キヨ",
"locale": "ja",
"iat": 1672704773,
"exp": 1672708373
}
検証内容
iss:IDトークン発行元がGoogle IDプロバイダのhttps://accounts.google.comになっている
aud:IDトークンの発行先がきよまるのOIDCお勉強アプリのクライアントIDになっている
nonce: 認証リクエストで指定した「abc」になっている
at_hash: 発行されたアクセストークンのハッシュと一致することを確認する
iat: IDトークンの発行日時許容できるくらい近いことを確認
exp: IDトークンの有効期限が過ぎていないことを確認
ここで正しいことが確認できればアクセストークンは正規のものになっているのでアクセストークンを利用してリソースを取得する。
これでIDトークンの入れ替えによる攻撃が防げます。※OAuth認証(OAuth2による認可+プロフィールAPIによる認証)の問題
4.アクセストークンを使ってUnserInfoエンドポイントにアクセス
HTTPヘッダにAuthorizationを指定してBearer文字列の後に半角スペースを入れてアクセストークンを入れる
リクエスト
ユーザ情報を取得するUserInfoエンドポイントにアクセスする
curl -H 'Authorization: Bearer **************************' https://openidconnect.googleapis.com/v1/userinfo
レスポンス
{
"sub": "**************************'",
"name": "キヨマル",
"given_name": "マル",
"family_name": "キヨ",
"picture": "**************************'",
"locale": "ja"
}
情報が取得できました!
おわり
実際に手を動かすことでOIDCの認可コードフローによる認可、認証の動きを理解できました。書籍中にも記載がありましたがOIDCとAuth認証(Auth2による認可+プロフィールAPIによる認証)の違いはIDトークンを認可コードと一緒に取得してIDトークンによりアクセストークンの妥当性を検証しているということがわかりました。
認可と認証周りはしっかりと抑えておかないとこれから困るところなので今回体系的に学べてよかったです。
Discussion
response_type=codeでトークンエンドポイントで取得したIDTokenにはat_hashが入っていますか?
サーバー間のやり取りで取得された場合はATとIDTokenの組み合わせが妥当なことは自明なのでこの組み合わせについての検証は必須ではありません。
at_hashやc_hashの検証が有用なのはフロントチャンネルで認可コードやアクセストークンを取得する場合にIDTokenのpayloadと署名検証により組み合わせが正しいことを確認できると言うもので、仕様でも認可エンドポイントからIDTokenが返されるときに必須、そうではない場合はオプショナルとなっているかと思います。このあたり、認可コードフローで取得するケースと混乱しないように整理できると良いでしょう。
ありがとうございます!
アドバイス頂いた内容についてまだ理解できていないのでこれから再度確認していきます!
なお、at_hashはIDトークンに入っておりました。