OAuthとOIDCを理解する【Azure AD B2Cの実践付き】

Security

認可を行うプロトコルである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

  • plaincode_challenge = code_verifier
  • S256code_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というリクエストパラメータでopenidGoogle 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/)のシリーズをお勧めします(^^)/

タイトルとURLをコピーしました