9
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自作OAuth2.0・OpenID Connectプロバイダを作ってみた

Posted at

はじめに

今回OpenID Connectの認証サーバーを自作してみたのでその記録を残しておきます。

システムの概要

OAuthの認可機能と、OpenID Connectによるシングル・サイン・オン(以下、SSO)機能を提供しています。
認証・認可のフローは下図のとおりです。

流れ

OpenID Connectの仕様に完全準拠させるのは大変なので、OpenID Connectの中核となるフローを実装しました。他の仕様については徐々に対応させていきたいと思っています。

アプリ画面

以下のリンクから、開発した認証サーバーを用いたSSOのデモを行うことができます。

https://auth-example.piny940.com

デモ画面

ボタンをクリックすると、認証ページに飛びます。アカウントを持っていない場合は、アカウントを作成することができます。

認証ページ

ログインすると、認可チェックのページにリダイレクトされます。(この画面のUIは追々改善したいなーと思ってます。)

認可画面

Approveすると、最初のデモサイトにリダイレクトされます。デモサイトはサーバーサイドで認証サーバーにトークンリクエストを送信しており、アクセストークンとIDトークンを取得しています。

認証済み画面

開発

今回の開発ではあえてOAuth向けのライブラリやフレームワークを使わずに1から実装をしてみました(JWTの生成などはライブラリを使用しています)。

フレームワークを使ったほうが楽だしセキュリティ的にも確実ですが、今回の開発は「OpenID Connectについての理解を深める」ということを大きな目的としていたため、あえてフレームワークを使わないという選択をしました。

設計

まずは、OpenID Connectの仕様に基づいて、認証サーバーのAPI・データベースの設計を行いました。

API設計

API設計はOpenAPIの形式で記述し、以下のサイトで公開しています。

https://auth-doc.piny940.com

OpenAPIの仕様書をYamlで書くのは面倒なので、OpenAPIの仕様書をTypeScriptのような記法で書けるTypeSpecを活用しました。

データベース設計

データベースは以下のように設計しました。

実装で悩んだ点

実装する際に悩んだのは、認可リクエストをどのようにバックエンドに送るか、ということです。

通常、Backendサーバーのページにユーザーが直接アクセスするのは稀で、JSでBackendサーバーのエンドポイントを叩くということが多いです。しかし、認可リクエストの場合は、リダイレクトレスポンスを返すようRFCに定められているため、JSでリクエストを送るという方法とは相性が悪いです。

これに対する方法として以下の2つの方法を考えました。

  • 通常通りJSでリクエストを送り、レスポンスが302であれば next/router を用いてリダイレクトする
  • Backendのエンドポイント( /authorize など)に直接アクセスする

1つ目の方法は、「通常通りBackendのエンドポイントはJSから叩く」という意味で一貫性があって分かりやすいです。一方で、「リダイレクト」というOAuthのコアな仕様に関する処理がFrontendのコードにまで入ってしまうため、セキュリティ的にはあまりよくありません。

2つ目の方法は、Backendのエンドポイントにユーザーが直接アクセスする、という点で若干の気持ち悪さがありますが、コアロジックをGoサーバーに集約することができるため、セキュリティ的に安心です。

以上を考えた結果、認可リクエストに関しては直接Backendのエンドポイントにアクセスする2つ目の方法を採用しました。

工夫した点

domainロジックの分離

Backendのコードは、今回レイヤード・アーキテクチャを採用し、domain層・usecase層・infrastructure層・api層に分割しました。
ドメインロジックをdomain層に集約することで、バグが起こりづらくし、また、テストもしやすくなりました。

コードの自動生成

上述の通り、Backendではレイヤード・アーキテクチャを採用しました。レイヤード・アーキテクチャでは、domain層、infrastructure層、api層でそれぞれの役割が異なるため、それぞれの層でモデルを定義する必要があります。また、各層の間の依存関係の注入(以下、DI)も大変です。これらをすべて手動で記述するのは大変ですし、ミスも起こりやすいです。

そこで、今回のプロジェクトでは以下の自動生成ツールを導入しました。

これにより、Backendのコードの自動生成を行うことができ、コードの品質を保つとともに、実装コストを大幅に削減することができました。

Frontendでもopenapi-ts/openapi-typescriptを用いて自動生成を行うことで、APIの型安全性を保つことができました。

今後について

現状、課題をいくつか抱えています。

  1. ID Tokenの有効性をサーバーに問い合わせることができない
    現状、1度発行したトークンは有効期限が切れるまで失効させることができないため、ID Tokenが漏洩した場合などでも、有効期限が切れるまでそのトークンを使い続けることができてしまいます。これに対応するために、トークンを失効させる仕組みを導入する必要があります。

  2. リフレッシュトークンが発行されていない
    現状、リフレッシュトークンを発行していないため、アクセストークンの有効期限が切れると再度認可フローを行う必要があります。これでは利便性が落ちてしまうため、リフレッシュトークンを導入したいと考えています。

こういった点の改善を徐々に行っていきたいと考えています。

おわりに

今回はOAuth2.0・OpenID Connectの認証サーバーを自作しました。開発を通して、OAuth2.0・OpenID Connect・JWTの仕様について理解を深めることができました。今後も、セキュリティに関する知識を深めていきたいと考えています。

9
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?