🔐

【図で解説】認証と認可の違いも分からなかった新卒がWEBアプリ作ってみた

2023/12/13に公開

こんにちは、COUNTERWORKS新卒インターンの@chiba_dです。9日目にもこちらの記事を書かせていただきました。気になった方はぜひこちらも読んでみてください。
https://zenn.dev/counterworks/articles/6fa484a58fa951

今回はセキュリティ関連の話です。

認証と認可、それにまつわるOAuth、OpenID Connectについて順番に解説します。

最後に新卒研修の中で社内アプリケーションを作った時の、Next-Authライブラリを使ったNext.jsでの認証・認可機能実装について話します。

想定読者

  • Webアプリケーションを作ったことがある・作ってみたい方
  • ログイン機能に興味がある方

はじめに

みなさん、「認証」と「認可」の違いを自信をもって説明できますか?

question

3か月前の自分:「パスワードを入力して、システムに入れるなら認証じゃないの...?」

以前の僕は上記のような理解レベルでした、ひどいですね。
同じ理解レベルの人はこの記事を読んで理解を深めてください!

背景

現代ではWebアプリケーションを作る時に認証・認可の違いを理解せずとも、豊富なライブラリによって認証機能は作れてしまいます。しかし不十分な知識で作られた認証システムはインシデントに繋がる恐れがあり、アプリケーションの信用を大きく損ねます。
cyber_attack
図. NICTERにおけるサイバー攻撃関連の通信数の推移 総務省の資料より引用

昨今、不正アクセスによる情報漏洩などを皆さんニュースサイトなどで見かけますよね。総務省の資料では、ここ数年のサイバー攻撃関連の通信数は5000億パケット前後の高い位置で推移しています。

もちろん上記のような不正アクセスでは攻撃者が絶対悪です。
しかし作る側にも責任は生じます。
自信を持って認証機能を作れるようにするために、この記事で認証の基礎を学んでいきましょう。

認証と認可について

まずは初めに出した問いの答え合わせです。

  • 認証とは、ユーザーがXXであるか確かめること
  • 認可とは、リソースにアクセスできる権限(アクセス許可)を与えること

※ XXには社内の人間、人間、ユーザー本人などの言葉が入ります。

順番に見ていきましょう。

認証

認証
図. 認証の例

認証の基本は、

  1. 知識情報(その人だけが知っているもの)
  2. 所有情報(その人だけが持つもの)
  3. 生体情報(その人であること)

を用いた本人確認です。
具体的には、

  1. パスワード、秘密の質問など
  2. スマートフォン、ICカードなど
  3. 顔の画像情報、指紋など

以上がそれぞれに該当します。
これらのうち複数の情報を組み合わせた認証を多要素認証と呼びます。

スマホでは顔認証・指紋認証・パスコード認証、Web,SNSでのベーシック認証(パスワード)は身近で分かりやすい認証の例だと思います。

CAPTCHA認証は人間であるかを確かめています。(画像の中の文字認証、車の写った画像を選ぶ認証、パズルのピースをはめる認証など)

他にも指紋情報、秘密の質問、スマートフォンなども広く認証に用いられています。

実際に存在する有名なWebアプリケーションでは二要素認証によって厳重な「なりすまし対策」が行われています。

認可

次に認可について解説します。

こちらも図を見ると分かりやすいかと思います。
認可
図. 認可の例

認可とは特定の条件を満たしたリソースに対するアクセス許可・権限を与えることを指します。
図の中の矢印でユーザーごとに権限を与えている箇所が認可です。

例えばBさんが認証に成功してシステムにログインできても、制限ユーザーの権限しか無ければ記事の閲覧しかできません。一方Aさんはシステムのすべての機能を利用できます。

認可機能に関しては、認可機能が無い認証のみシステムを考えると重要性が分かります。アカウントを持ってさえいれば全てのシステムにアクセスできるSNSを想像してみてください。他のユーザーのプロフィールが自由に編集できるSNSを皆さんは使いたいですか?
論外ですね。

まとめ

認証とは誰であるか確かめること、認可とはリソースへアクセスする許可・権限を与えることです。

認証と認可は片方のみで使用されることもありますが密接に関わっています。
通常のWebアプリケーションでは両方の機能を組み合わせて安全性を確保しています。組み合わせ方には様々な種類が存在し、時代によって変化していきます。

より安全なアプリケーションを作ることを心がけましょう。

OAuth, OpenIDConnectとは

認証と認可に関連したOAuth、OpenID Connectという名前の概念が存在します。

これらについても学んでおきましょう。

OAuth

OAuth
図. OAuthのイメージ図

もう図を作るのにも慣れてきました。

認可の流れとしては、緑→紫の順番です。
OAuthには下記の4つの主要なグラントと呼ばれるプロセスが存在します。

  • Authorization Code Grant
  • Implicit Grant
  • Resource Owner Password Credentials Grant
  • Client Credentails Grant

ここでは具体的に上2つの認可コードグラントとインプリシットグラントについて一連のフローを示します。

認可コードグラント(Authorization Code Grant)

OAuthとはリソースオーナー(男性)が認可サーバー(技術記事サービス)に許可を与えて、クライアント(SNSサービス)に認可サーバー(技術記事サービス)への権限委譲を行うプロセスです。

認可コードグラントの一番の特徴は認可サーバーが送られた認可コードを確認してからアクセストークンを与える箇所にあります。

ざっくりと図に沿って認可コードグラントの全体の流れを説明すると、

OAuth
図. OAuthのイメージ図

  1. 男性が、SNSサービスで OAuth認可用のURIにアクセスします。
  2. SNSサービスはリダイレクト機能を使って、男性に技術記事投稿サービスの認可用URIにアクセスさせます。その際URIには認可に必要なパラメータを付与しておきます。
  3. 技術記事投稿サービスの認可サーバーが男性を認証し、続いて男性にSNSサービスが技術記事投稿サービスの記事にアクセスすることを許可(認可)してもらいます。
  4. 許可された場合、技術記事投稿サービスの認可サーバーはリダイレクト機能を使って、男性にSNSサービスのコールバックURIにアクセスさせます。その際URIには認可コードを含めておきます。
  5. SNSサービスはその認可コードと初めに認可コードを取得した際のURIを用いて技術記事投稿サービスにアクセストークンを要求します。
  6. 技術記事投稿サービスの認可サーバーが、
  • SNSサービス
  • 認可コード
  • 認可コード取得URI
    を確認します。
  1. これまでのフローで取得・使用したものと一致していれば技術記事投稿サービスからSNSサービスにアクセストークンが渡されます。
厳密には以下のプロセスが定義されています。(興味のある方は読んでください)
  1. クライアントは、リソースオーナーのユーザーエージェントを認可エンドポイントに指示することでフローを開始します。クライアントはクライアント識別子、要求されたスコープ、ローカルステート、および認可サーバーがユーザーエージェントをアクセスが許可された場合(または拒否された場合)に戻すためのリダイレクションURIを含めます。
  2. 認可サーバーは、リソースオーナー(ユーザーエージェントを介して)を認証し、リソースオーナーがクライアントのアクセス要求を許可するか拒否するかを確立します。
  3. リソースオーナーがアクセスを許可したと仮定すると、認可サーバーはリダイレクションURIを使用してユーザーエージェントをクライアントに戻します。リダイレクションURIには、クライアントが前もって提供した認可コードと任意のローカルステートが含まれます。
  4. クライアントは、前のステップで受け取った認可コードを含めて、認可サーバーのトークンエンドポイントからアクセストークンを要求します。リクエストを行う際、クライアントは認可サーバーと認証します。クライアントは、認可コードを取得するために使用されたリダイレクションURIを検証するために使用されるURIも含めます。
  5. 認可サーバーはクライアントを認証し、認可コードを検証し、リソースオーナーがステップ3でクライアントにリダイレクトする際に使用されたURIが受け取ったURIと一致することを確認します。有効であれば、認可サーバーはアクセストークンと、オプションでリフレッシュトークンを含む応答を返します。

インプリシットグラント(Implicit Grant)

インプリシットグラントについて図を交えて説明します。

基本の流れは認可コードグラントと同じですが、インプリシットグラントでは認可コードの受け渡しをしません。

OAuth
図. OAuthのイメージ図

  1. リソースオーナー(図の男性)がSNSサービスへの技術記事サービスへのアクセスを許可した場合、技術記事サービスは男性にアクセストークンを含むURIを渡します。
  2. 技術記事サービスから渡されたURIに男性がアクセスすると、要求する情報が含まれたWebページとスクリプトが返され、男性のローカル環境でスクリプトが実行されます。
  3. スクリプトから取り出されたアクセストークンを男性がSNSサービスに渡すことでSNSサービスから技術記事サービスへのアクセスが技術記事サービスによって認可されます。
厳密には以下のプロセスが定義されています。(興味のある方は読んでください)
  1. クライアントは、リソースオーナーのユーザーエージェントを認可エンドポイントに向けてフローを開始します。クライアントには、クライアント識別子、要求されたスコープ、ローカルステート、および認可が許可された(または拒否された)場合に認可サーバーがユーザーエージェントを送り返すためのリダイレクトURIが含まれています。
  2. 認可サーバーはリソースオーナー(ユーザーエージェントを介して)を認証し、リソースオーナーがクライアントのアクセスリクエストを許可するか拒否するかを確立します。
  3. リソースオーナーがアクセスを許可した場合、認可サーバーはリダイレクトURIを使用してユーザーエージェントをクライアントに戻します。リダイレクトURIには、URIフラグメントにアクセストークンが含まれています。
  4. ユーザーエージェントは、ウェブホスト型のクライアントリソースに対して要求を行い([RFC2616]によれば、フラグメントは含まれません)、ユーザーエージェントはフラグメント情報をローカルに保持します。
  5. ウェブホスト型のクライアントリソースは、フラグメントを含む完全なリダイレクトURIにアクセスできるウェブページ(通常は埋め込まれたスクリプトを含むHTMLドキュメント)を返します。ユーザーエージェントはフラグメントからアクセストークン(およびその他のパラメータ)を抽出します。
  6. ユーザーエージェントは、ウェブホスト型のクライアントリソースによって提供されたスクリプトをローカルで実行し、アクセストークンを抽出します。
  7. ユーザーエージェントはアクセストークンをクライアントに渡します。

まとめ

まとめるとOAuthでは、サービスAの部分的な操作権限をサービスBに委譲しています
こちらは利便性を得られる反面、重要な操作権限を不特定多数の外部サービスに渡してしまうことでアカウントの乗っ取り被害、情報漏洩につながるといったリスクを伴います。

OpenID Connect (OIDC)

OpenID Connectについて図を見てみましょう。

OIDC
図. OpenID Connectのイメージ図

OpenID ConnectはOAuthを拡張して作られた概念です。
流れとしては赤→青→緑→紫の順番で認証が行われます。

認可コードフロー

openid-connect-core-1_03.1.1 Authorization Code Flow Stepsに記述されている認可コードフローでは以下のプロセスが定義されています。

・クライアントとは図の右上のシステム
・エンドユーザーとは左上の男性
・認可サーバーとは下のOpenID Provider

に置き換えて読んでみてください。

OIDC
図. OpenID Connectのイメージ図

  1. クライアントは、希望するリクエストパラメータを含む認証リクエストを準備します。
  2. クライアントはリクエストを認可サーバーに送信します。
  3. 認可サーバーはエンドユーザーを認証します。
  4. 認可サーバーはエンドユーザーの同意/認可を取得します。
  5. 認可サーバーはエンドユーザーをクライアントに認可コードとともに戻します。
  6. クライアントはトークンエンドポイントで認可コードを使用してレスポンスを要求します。
  7. クライアントは、レスポンス本体に含まれるIDトークンとアクセストークンを含むレスポンスを受け取ります。
  8. クライアントはIDトークンを検証し、エンドユーザーのサブジェクト識別子を取得します。

認可コードフローでは認可コードを使用してトークンを取得し、図の男性(エンドユーザー)を識別しています。

インプリシットフロー

またインプリシットフローと呼ばれる別のフローも存在します。

OIDC
図. OpenID Connectのイメージ図

こちらも

・クライアントとは図の右上のシステム、
・エンドユーザーとは左上の男性、
・認可サーバーとは下のOpenID Provider

に置き換えて読んでみてください。

  1. クライアントは、希望するリクエストパラメータを含む認証リクエストを準備します。
  2. クライアントはリクエストを認可サーバーに送信します。
  3. 認可サーバーはエンドユーザーを認証します。
  4. 認可サーバーはエンドユーザーの同意/認可を取得します。
  5. 認可サーバーはエンドユーザーにIDトークンと、要求があればアクセストークンを含んだ形でクライアントに戻します。
  6. クライアントはIDトークンを検証し、エンドユーザーのサブジェクト識別子を取得します。

インプリシットフローでは、認可コードフローに比べて認可コードによるレスポンスの要求が無いためプロセスの総数が少なくなります。
この記事の図で示されているフローはこのインプリシットフローに該当します。

右上のシステムはIDトークンとアクセストークンを含む要求を行い、OpenID Providerから両トークンが含まれたレスポンスを受け取り、エンドユーザーである男性を識別します。

まとめ

GitHubやGoogleなどのOpenID Providerによる認証機能を利用することで、

  • 直接利用したいアプリケーションにパスワードを設定しなくてよいためパスワード管理が楽
  • パスワードを利用アプリケーション内DBに保持しないため安全性が確保される

というメリットがあります。

Next-Authを用いた実装

ここまでで、上記の言葉について十分に説明できるようになったこと思いますので、実際に私が作ったWebアプリケーションの認証・認可機能について説明します。

新卒研修ではWebアプリケーションをNext.js,Prismaを用いて作成しました。

社内のノウハウや毎年の研修内容をスタックするアプリケーションです。こちらはSlackで流れてしまった情報を掘り返す労力を削減し、毎年の研修のクオリティを担保するという目的で作られました。

今回はその中で認証と認可部分についてのみに絞って説明します。

認証・認可機能の実装

認証は、Next-Authライブラリを用いてGoogle、GitHubのOpenID Connectを利用しました。Next-Authライブラリを用いることでセッション情報とトークンを簡単に管理することができるため、今回採用しました。

認可はOpenID Providerから渡されるセッション情報を用いてミドルウェアで行います。

OpenID Connectによる認証認可のみでは社外の人でもアクセスできてしまうため、OpenID Providerに要求して返ってきたメールアドレスを含むセッション情報とPrisma内部に保存してあるメールアドレスとを比較することで、社内の人間のみがアクセスできるように設計しました。

ここではセッション情報内でユーザーごとにroleを追加で付与することでページへのアクセス権限を分離しています。この権限はdb内のメールアドレスに紐づいたroleカラムの内容を元に付与されるため、アクセスできるページをユーザーごとに制御できるようになっています。

//next-auth.d.ts

declare module "next-auth" {
  interface Session {
    user: {
      id?: number,
      role?: string,
    } & DefaultSession["user"]
  }
}

こちらも図で示します。
社内研修アプリ
図. 実際に設計した認証認可の仕組みのイメージ図

流れは、赤→紫→青の順番です。

ミドルウェアでTokenを持っているかを確認することで全体へのアクセスを認可しています。

最終的にNext-Authがセッション情報のrole を確認することでStudentなら赤のページへのアクセスを認可、Teacherなら緑のページへのアクセスを認可しています。

またPrisma内部にはパスワードを保存しておらず、外部から不正アクセスに成功してもメールアドレスの情報しか取得できません。

しかしこちらの認証・認可方法を設計して実装したことで、大幅な工数の増加が発生してしまいました。
危うくアプリケーションが完成しないという状況に陥りそうでした...反省しています。

おわりに

この記事では認証と認可の違い、またOAuth、OpenID Connectについて図を用いて解説しました。

この社内Webアプリケーションの設計と実装を行ったこと、記事を執筆したことで、僕自身の認証基盤に関する理解も大きく深まりました。
記事中で軽く触れたOAuthのGrant、OIDCのFlowについて、またCSRF対策、XSS対策なども実装したのでいずれ解説できたらと思います。

アプリケーションのセキュリティ対策が重要視される現代で、知識を身に着けて共に良い開発を行いましょう!

We are hiring!!

認証と認可に詳しくなりたいCOUNTERWORKS では一緒に働く仲間を絶賛募集中です。
今後の更なる成長のためには圧倒的に仲間が不足しています。皆さまのご応募お待ちしております!

https://counterworks.co.jp/recruit/?utm_source=zenn&utm_medium=referral&utm_campaign=advent-calendar-2023&utm_content=13

参考

  1. https://dev.classmethod.jp/articles/authentication-and-authorization/
  2. https://solution.netone-pa.co.jp/blog/346
  3. https://datatracker.ietf.org/doc/html/rfc6749
  4. https://openid.net/specs/openid-connect-core-1_0.html
  5. https://ritou.hatenablog.com/entry/20120206/1328484575
  6. https://ritou.hatenablog.com/entry/2018/11/12/110613
COUNTERWORKS テックブログ

Discussion