【OpenID Connect】ログイン情報連携させる時ってどうするの?
概要
メンターのヒグです!今日はメンティーのRockyさんから、「OpenID Connect」について教えて欲しいというご要望がありましたので、そのやり取りの模様を記事にして、ご紹介したいと思います。
本題
Rocky:ヒグさん、今日はお忙しいところ、どうも有難うございます!よろしくお願いします!
Higu :Rockyさん、こちらこそよろしくお願いします! で、今日はなにか「OpenID Connect」について悩んでいるってお聞きしたんですが。
Rocky:そうなんですよ、実は僕が担当している案件で、通常のログイン方法に加え、それとは別にとあるサービスのIDを使ってログインできるようにさせたいという要望がクライアントからありまして、OPpenID Connectという仕様を使って実装することになったのですが、僕はこういう認証系の実装って今まで経験がなく、よく分からなくて、、、
Higu :なるほど、そういうことでしたか。システムの構成はどんな感じなんですか?
Rocky:下の画像のようなイメージなんですよね。僕が担当している案件のサイトがあって、そしてそのとあるサービスのAPIがあり、その間に中継点として認証用のAPIサーバーも新たに立てるっていう感じなんです。
Higu :なるほど、なるほど。こういう感じなんですね。で、分からない点ってどこになりますか?
Rocky:いや、もう全部っすね(笑)。誰がどんな役割を担い、どんなものが受け渡されているのかっていうのが、参考書とか読んだんですけど、今いちピンとこなくてですね。。
Higu :OKです! では一つずつ流れを追いながら、自分だったらどういう感じで実装するのかっていう視点で、ざっくり解説してみますね!で、Rockyさん、まず前提として、このサイトっていうのはサーバーサイドレンダリングではなくSPAという認識で大丈夫でしたか?
Rocky:はい、大丈夫です。
Higu :OKです! SPAとサーバーサイドレンダリングで、若干実装の仕方は異なってきますので。
Rocky:そうなんですね。
Higu :では見て行きましょう! で、最終的なゴールを初めに確認しておくと、そのあるサービスのAPIが発行するIDトークンをいかに安全に、サイト閲覧者のブラウザに渡すかってことが今回の最も大事なミッションになります。 その安全性を担保するために、色々なことを仕込んいくことになるので。あっ、IDトークンっていうのは、認証の結果得られるトークンのことです。認証というのは、誰であるかを判明させるものですね。
Rocky:なるほど。そうなんですね。
Higu :はい。では、まず恐らくRockyさん担当のそのWebサイトに、そのサービスでログインさせるためのボタンがあるんですよね?
Rocky:そうです!
Higu :じゃぁそれをポチっとすると、あるサービスのAPIにリクエストが投げられますと。(Ⅰ)
Rocky:なるほど、じゃぁこういうイメージってことですか? このリクエスト投げる時点では、中継APIを経由しないと。
Higu :そうですね。
Rocky:なるほど。
Higu :で、次にそのあるサービスのAPIが、Rockyさん担当Webサイトにリダイレクトさせるとともに、URLにクエリでワンタイムパスワードを返します。(Ⅱ)
Higu :これは例えば、ここでIDトークンをクエリで渡してしまうと、丸見えになってしまって、とても危険ですよね。なので、ここでは私だったらクエリにワンタイムパスワードを乗せて返すと思います。ちなみにここはOpenID Connectの仕様ではなく、私なりのソリューションです。あとここで大事なのは、リダイレクトさせるURLをきっちり制限しておくことと、ワンタイムパスワードには有効期限を設定しておくことですね。
Rocky:他の人に盗まれたりしないために、ってことですね!?
Higu :そうです!で、このクエリでワンタイムパスワードを返すタイミングで、あるサービスのAPIから中継APIにもあるものを渡させます。それは、2つあって、1つは先ほどのワンタイムパスワードと全く同じもの。そしてもう一つはとJWT(ジョット)という形式のIDトークンになります。(Ⅲ)
Rocky:このIDトークンっていうのは、最終的にWebサイト閲覧者のブラウザに渡したいものですよね?
Higu :そうです! これを中継APIに渡します。
Rocky:ふむふむ。
Higu :ここで、先ほどRockyさん担当Webサイトにもワンタイムパスワードを返してましたよね?これをそのブラウザから、中継APIに投げてるようにしてみましょうか。(Ⅳ)
Rocky:ふむふむ、となると中継APIに、今2つのワンタイムパスワードがあるってことになりますね。もしかして、この2つが同じものかを検証して、同じだったらHappyになれちゃうってやつでしょうか?
Higu :さすがRockyさん、その通りです!2つのワンタイムパスワードが同じものだと確認できた場合は、中継APIからWebサイトへIDトークンを、APIレスポンスとして返します。(Ⅴ)
Higu :こうすることでURLにはIDトークンには乗って来ないから安全ですよね。そしてこの時点で、もうワンタイムパスワードは破棄してしまいます。
Rocky:なるほど! では、このIDトークンをもって、無事Webサイトにログインできるということになるんですか?
Higu :いや、ここからが最終関門、OpenID Connectの真骨頂です。今このWebサイト閲覧者が手にしたIDトークンっていうのは、本物なのかどうかっていうのをまだ確認できていないですよね?このIDトークンが確かに「あるサービスのAPI」によって発行されたIDトークンであるってことを証明したいですよね? なので、ここからさらにもう一度このIDトークンを中継APIに、POSTします。(Ⅵ)
Rocky:えっ、マジっすか!? せっかくもらえたのに。。
Higu :もしワンタイムパスワードだけで認証を済ませてしまうと、共有PCを使っていた場合、ブラウザの履歴に残ったURLなどから不正をされてしまう可能性がありますよね。なので、(V)のタイミングでIDトークンを取得したあとに、使用済みワンタイムパスワードを無効化し、ここから最後の最終試験に向けてIDトークンを旅立たせるというわけなんです!
Rocky:なるほど! 厳しい道のりを経た後にこそ、安心な世界が待っているというわけですね!で次はどうなるのでしょうか?
Higu :ここでというか、まぁタイミングのパターンは色々とあるんですが、あるサービスのAPIから中継APIに鍵を渡します。(Ⅶ)
Rocky:鍵ですか?何をする鍵になるんでしょう??
Higu :署名を検証するための鍵になるんですが、そうですね、鍵の説明の前に、JWT形式のIDトークンというのものが、どういう構造になっているか見てみましょう。JWT形式のIDトークンというのは、下図のようにヘッダー、ボディー、署名という3部構成になっているんですよね。
で、一番下の署名っていうのは、真ん中のボディー部分を鍵で暗号化することで生成されるんですね。この鍵を中継APIにも渡すってことです。この鍵で、Webサイトのブラウザから返ってきたIDトークンに対して検証をするんです。検証のやり方は、署名を生成する時と同じです。ボディー部分を鍵で暗号化して生成された署名が、元々「あるサービスのAPI」から渡されたIDトークンの署名と一致するか否で、検証しているんです!
もし一致していなかったら、ボディー部分が改竄されてるっていうことになるんで、これをもって検証したと言えることができるんですね!
一致していたら、Webサイトには成功のステータスを返却すれば、晴れてこれでようやく念願の「あるサービスのログイン情報」で、Rockyさん担当のWebサイトにもログインできるってことになったわけですね!(Ⅷ)
Rocky:いや〜、行ったり来たりで、ものすごい長い道のりでしたね(笑)
Higu :もう少しセキュアにしようと思えば、あと二箇所くらいは改善点があるのですが、ちょっと今日は長くなったんで、続きはまた次回にしましょうかね。
Rocky:そうですね、また次回どうぞよろしくお願いします!ご教示頂いた内容は復習しておきます!すごい分かりやすくて勉強になりました!今日はどうも有難うございました!
Higu :こちらこそ有難うございました!
最後に
いかがだったでしょうか?OAuth2.0やOIDCの話に関してはいろいろな活用方法がありますが、自分なりにこうするという考えを踏まえて説明してみました。また、普段からこういう感じでカジュアルに技術に関することも、そうでないことも教えているので興味がある方はメンタープランの方をチェックしてみてください。最後にこれからも投稿していきますので是非フォローの方お願いいたします。