この記事はアドベントカレンダー2025 18日目の記事です
はじめに
こんにちは!! 24Bのmumumuです。
今回はOauthでちょっとしたアプリを作ってみたのでその際に面白かったOauthの仕様について紹介します
できるだけ間違いがないように参考文献を付けながら話していきます。何度かRFCという言葉が登場しますがこれはOauthの仕様を定めた文書のことです。
経緯
私はtrap内でアカウント機能のあるwebアプリを何度か作ったことがあります。
しかし!一度も認証について考えたことがありません。それはtraP内のデプロイ先であるNeoShowcaseが認証を行ってくれ、ヘッダーにユーザー名を加えてくれるからです。(素晴らしい環境ですね)
とはいえ少しは認証に触れてみたい!ということで今回はGoogleのOauth認証を使ってアプリを作ってみました。
どんなアプリでOauth認証を導入してみたか
ところで私は最近 alt + spaceのショートカットをよく使っています。ChatGPTアプリを前面に出したり隠したりできて便利なのでおすすめです。
さて、このネイティブアプリ特有のショートカット機能を使ってalt + cが押されたらGoogle calendarにクリップボードの内容を登録するアプリを作ってみました。
クリップボードから日付っぽい文字列をルールベースで検出し、それを入力欄に自動でセットします。あとは送信ボタンを押すだけで、予定がカレンダーに登録されるという流れです。
当然カレンダーに書き込むにはユーザーの許可が必要なので、ここで OAuth が登場します。
Oauthってなあに?
Oauthとはアプリケーションにユーザーのデータへ必要な分だけアクセス許可を与える仕組みです。
もしアプリにユーザーのパスワードを教えてしまうと不必要な権限まで与えてしまい、なんでもできてしまいます。
なので最小限必要な事だけ許可されたアクセストークンを認可サーバーが発行します。
(例:googleカレンダーに予定を追加するだけのアプリにアカウントのパスワードを教えてしまったらgoogle driveやgoogle photoへアクセスして情報を取ることも可能になってしまうので カレンダーへの予定の追加のみを許可する)
賢いですね!!
ネイティブアプリのOauthのおおまかな流れ
ネイティブアプリにおけるGoogleのOauthの大まかな流れです。利用する認可サーバーや端末では異なる可能性があります
- まずユーザーがアプリ上で「ログイン」や「連携」ボタンを押します。
- アプリは認証用のurlを作成してデフォルトブラウザでユーザーに開かせます。
- ブラウザでユーザーはアプリケーションの要求する権限を確認し許可を同意します。
- 認可サーバーが
codeと呼ばれるものをネイティブアプリが起動しているローカルサーバーに送ってきます。 codeを受け取ったアプリはそれを認可サーバーに送りアクセストークンと交換してもらいます。

安全のために注意しなければならない点はいくつかあるのですが基本的な流れはこんな感じです。
ネイティブアプリのClient Secretについて
バックエンドを持つアプリケーションとは違いネイティブアプリは配布物(実行ファイル)にすぎないので、アプリに埋め込んだ値は秘密にできません。
そのため client secret のような機密情報を入れても、「本当にそのclient idを持つ正当なアプリからのリクエストかどうか」の確認には使えません。
ネイティブアプリに関するOauthの仕様であるRFC8252(8.5)にもclient_secretをクライアントの証明に用いることは推奨されていません。
ネイティブアプリは public client として扱われ、クライアント自身をシークレットで証明しない前提になっています。
一方で、Google Oauthではネイティブアプリにたいしてもclient secretを要求されます。
サーバーを持たないネイティブアプリは実行ファイルに埋め込む形になると思いますが、セキュリティ的な意味はおそらくないと思います。
デフォルトブラウザで開く理由
Google認証ページをユーザーに表示する方法としてアプリ内でそのぺージを開くという方法(WebView)も考えられます。しかしこれは推奨されていません
どうしてだめなのでしょうか?
資格情報をアプリが盗めてしまう
WebViewはアプリの一部として動作するため、アプリがユーザーの資格情報とCookieを取得してしまう可能性があります。
RFC8252(8.12)でもWebViewは推奨されておりません。
同様の理由からGoogleでも推奨されていません(参考)
本物のサイトか区別できない
WebViewではURLバーが見えないため、ユーザーはそのログイン画面が本物か判断できません。
URLが確認できない状態で資格情報を入力させるのは問題があります。
パスワードはパスワードマネージャーを使って入力しましょう。手入力はNGです。
毎回ログインが必要になる
デフォルトブラウザを起動すれば既にgoogleにログイン済みなのに対してWebViewだとCookieを共有しないため毎回ログインしなおす必要があり、UX的にも問題があります
認可サーバーからのcodeの受け取り方
ユーザーが連携ボタンを押すと認可サーバー(Google)はアプリケーションにアクセストークンと交換できるcodeというものを送信します。
ネイティブアプリはこのcodeをどのように受け取ればいいでしょうか?手法は主に以下の3つです。
カスタム URI スキーム
com.example.app:/callbackのようなurlをOSに登録してそこにリダイレクトしてもらう手法
特徴
端末によっては複数のアプリが同じURLを登録可能だから非推奨らしい
(RFCにそのような文言は見つけられませんでしが非推奨と言っているサイトが多かったです。)
Claimed Https Scheme URI
- httpsのURLだがOSに登録されていて開くとアプリに遷移する。
- 例: twitterのURLを押すとアプリが起動するあれ
特徴
- OSがこのURLはアプリのものと保証してくれる
- RFC8252(7.2)でclaimed httpsが推奨されている
ループバック IP アドレス リダイレクト
- アプリが一時的にローカルサーバーを立て、そこにリダイレクトしてもらう方法
- 例:
http://127.0.0.1:51004/
Google Oauthではどれが推奨されているか
ではGoogle Oauthではどれがいいのでしょうか?
RFC 8252 では claimed HTTPS scheme が推奨されていますが、
GoogleのOauthに関してはプラットフォームごとに推奨される方法が異なり、windowsやmac用のアプリならループバックが推奨されています。(IOSやAndroidはだめ)
GoogleのOauthでは端末によって推奨される手法が異なっていて注意して実装する必要がありそうです。
ループバックリダイレクトの注意
意外な罠なのですがリダイレクトurlをhttp:localhostにするとhostsファイルを編集されることで別のurlに飛ばすことが可能になってしまいます。なので127.0.0.1が推奨されています。
なんで一発でアクセストークンを渡さないの?
codeというのはユーザーが承認すると認可サーバーが送ってくるもので、これと引き換えにアクセストークンを入手できます。
ではなぜ一発でアクセストークンを渡さないのでしょうか?面倒です。
上で紹介したようにユーザーが承認した後の認可サーバーからの返答はブラウザを経由してlocalhostにリダイレクトされます。そのためURLがアドレスバーに表示されてしまい、履歴に残ったり拡張機能や別プロセスに覗き見られる可能性があります。
そこで直接アクセストークンを渡さず引換券としてcodeを渡します。
code はブラウザを経由して渡されるため、第三者に見られる可能性があります。
そのため、code 単体ではアクセストークンに交換できない仕組みが必要になります。
そこで実はPKCEという手法が登場します。
PKCE(ピクシー)について
実は最初の認可リクエスト(ユーザーが連携ボタンを押した)の際にcode_verifierという値を生成し、これをSHA-256でハッシュ化した値を認可サーバーに送り付けています。
最初のアクセスの際
- アプリがランダムな文字列
code_verifierを生成する - これをSHA-256でハッシュ化して
code_challengeとして認可サーバーに送ります
トークン交換時
codeを送るときにcode_verifierを送る- 認可サーバーは
code_verifierをハッシュ化しcode_challengeと同じ値になることを確認する - 一致していたらアクセストークンを発行する
このようにして初めのアクセスと同一クライアントであることを確認します。
これでcodeを盗まれてもcode_verifierが分からないのでトークン交換ができません。
そのためにわざわざ二回やり取りする必要があったんですね~
アクセストークンとリフレッシュトークンの管理について
ユーザーがアプリを使うたびに毎回Googleの認証画面に遷移してもいいのですが、それではUX的によくありません。
そのため、アクセストークンの有効期限が過ぎていない間は同じものを使いまわしましょう。
ただし、アクセストークンは短命(Googleは1時間)なので、期限が切れたらそのままではAPIを叩けません。
そこで登場するのが リフレッシュトークンです。リフレッシュトークンを持っていれば、ユーザーを再度ログイン画面に飛ばさなくても、 新しいアクセストークンを自動で取り直すことができます。
保存方法
ネイティブアプリはOSに安全に保存しておいてもらいましょう。
Rustではkeyringというライブラリを用いることでOSに保存しておくことが可能です。
保存しておくものは
- アクセストークン
- リフレッシュトークン
- それぞれの有効期限
あたりだと思います。有効期限を確認しリフレッシュトークンの再取得が必要かどうか見極めましょう。
ちなみに
ちなみにリフレッシュトークンを用いてアクセスをすると新しいリフレッシュトークンがもらえる場合は置き換える必要があります。
Googleはネイティブアプリの場合新しいリフレッシュトークンは含まれていないので気にする必要はありません。
最後に
どうでしたか?先人の安全にしようという努力が見えて非常に面白いですよね!!
実はこれだけでは安全とは言えずいくつかの攻撃に対して脆弱になってしまいます。気になる方は調べてみてください。
今回はOauthの特に利用者側について勉強してみたのですがOIDCや認可サーバーも面白そうですね
