0
0

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|Auth0に入門する React + Ktor

Posted at

クライアント(React)でAuth0からアクセストークンを取得し、そのアクセストークンをAuthorizationヘッダーに付与して保護されたリソースサーバー(Ktor)にリクエストし、Ktorでアクセストークンを検証する仕組みを作成します。

著者が開発中に遭遇したエラーを再現しながら進めていきます。OAuth2.0はまだまだ勉強中の身ですので、間違いなどありましたら、ご指摘いただけると幸いです。

サーバーサイドフレームワークにはKtorを使用しています。(kotlinサーバーサイド開発が個人的なトレンドです。)

Ktorの需要があまり高くないかもしれませんが、Auth0上での設定やアプリケーションコードの本質的な処理内容はフレームワークに依存せず同じです。

フレームワークに依存する細かいアプリケーションコードは他の記事を参照して頂ければと思います。

Auth0のアカウント作成

スクリーンショット 2025-01-02 16.09.10.png

  • Account Type: Other
  • I need advanced settings: Checked

スクリーンショット 2025-01-02 16.10.11.png

Auth0でテナント作成

  • Tenant Domain: 良い感じの名前
  • Region: Japan

スクリーンショット 2025-01-02 16.12.38.png

Auth0でSample Appを作成

Tenantを作成すると、Auth0の素晴らしいオンボーディングが始まるので、これを体験していきます。

Auh0のダッシュボードでポチポチとクリックするだけで、OAuth2.0が組み込まれたReactを起動できます。

Step 1

プラットフォームのタイプを選択します。ReactなのでSPAです。

  • Select a platform for your app: Single-Page App
  • Select the technology: React

スクリーンショット 2025-01-02 16.13.54.png

Step 2

ログイン/サインアップページのUIをカスタマイズできます。

何も設定を変えずにContinueを押しても大丈夫です。(著者は Social Connections に GitHub を追加する変更だけ行ないました。)

スクリーンショット 2025-01-02 16.15.28.png

スクリーンショット 2025-01-02 16.15.45.png

Step 3

Try Loginを押すと、カスタマイズしたUIで、ログイン/サインアップページのプレビューを閲覧できます。

スクリーンショット 2025-01-02 16.18.11.png

スクリーンショット 2025-01-02 16.17.40.png

Step 4

DOWNLOAD SAMPLE APPボタンを押すと、Reactをダウンロードできます。

画面右に記載の設定は後ほど行います。

スクリーンショット 2025-01-02 16.18.38.png

スクリーンショット 2025-01-02 16.18.59.png

Auth0でApplicationとAPIを作成

ApplicationはAuth0上でクライアントアプリケーション(Reactにあたる)を管理するコンポーネント、APIはAuth0上でリソースサーバー(Ktorにあたる)を管理するコンポーネントです。

Application作成

Applications > Applicationsタブを開きます。

ApplicationDefault Appが既に作成されているので、これを使用します。

スクリーンショット 2025-01-02 16.20.33.png

API作成

Applications > APIs > Create API から作成します。

スクリーンショット 2025-01-02 16.32.38.png

  • Name: 良い感じの名前
  • Identifier: 一意の識別子(URI)
    • アクセスを許可するリソースサーバーをTenant内で識別するための値です
  • JSON Web Token (JWT) Profile: Auth0
  • JSON Web Token (JWT) Signing Algorithm: RS256

スクリーンショット 2025-01-02 16.33.37.png

これでAPIの作成も完了です。

スクリーンショット 2025-01-02 16.34.16.png

Reactの起動

ダウンロードしたReactを npm install && npm start で起動します。

スクリーンショット 2025-01-02 16.31.22.png

Callback URL mismatch

ログインボタンを押すと、先ほどプレビューで閲覧したログインページを表示して欲しいところですが、エラーページが表示されてしまいます。

スクリーンショット 2025-01-02 21.02.31.png

エラーページには以下のように記載されています。

Callback URL mismatch.
The provided redirect_uri is not in the list of allowed callback URLs.
Please go to the Application Settings page and make sure you are sending a valid callback url from your application.

「Auth0で許可されているRedirect URI(Callback URL)」と「クライアント(React)が指定したRedirect URI(Callback URL)」が一致していない、とのことです。

Redirect URIはAuth0での認証結果を返却するURIを指します。

Redirect URIは、Auth0でもクライアント(React)でも何も設定していないので、当然と言えば当然です。オンボーディングの最後の画面の右に記載されていた内容が、この設定に関する内容です。

Monitoring > Logs でエラー調査

今回のエラーは、エラー原因が画面に表示されていましたが、エラーが起きた際は、Auth0のダッシュボードから原因を調査することが可能です。

スクリーンショット 2025-01-02 21.26.12.png

先ほどと同じ主旨の内容が記載されています。

Callback URL mismatch. http://localhost:3000 is not in the list of allowed callback URLs

スクリーンショット 2025-01-02 21.30.34.png

Auth0 Application と Reactの設定変更

クライアント(React)でAuth0からアクセストークンを取得する仕組みを作成します。

Auth0 Applicationの設定変更

Default Appの設定を変更します。

スクリーンショット 2025-01-02 16.20.33.png

Settings > Application URIs の以下の3つの入力欄にhttp://localhost:3000と入力します。

  • Allowed Callback URLs
  • Allowed Logout URLs
  • Allowed Web Origins

localhostなので、httpsではなく、httpです。

スクリーンショット 2025-01-02 16.22.26.png

Reactの設定変更

auth_confg.jsonの値を変更します。

{
  "domain": "**********",
  "clientId": "**********",
  "audience": "**********"
}

domainとclientIdはAuth0のapplicationに記載されています。

スクリーンショット 2025-01-02 16.28.13.png

audienceはAPIのIdentifierです。

スクリーンショット 2025-01-02 16.34.16.png

Application authentication methods

この段階でログインボタンを押すと、ログインページが表示されます。

スクリーンショット 2025-01-02 16.17.40.png

サインアップ画面に移動し、ユーザー登録も成功しますが、アクセストークンの取得に失敗し、ホーム画面にリダイレクトされません。

エラーのlogを見ても、Failed Exchangeとかfeacftとかの記載だけで、良く分かりません。

スクリーンショット 2025-01-02 23.16.40.png

この問題は修正されるかもしれませんが、最初に作成されるDefault AppのアプリケーションタイプがSPAであるにもかかわらず、Application authentication methodsがClient Secret (Post)に設定されています。

SPAの場合、ここはNoneであるべきなので、設定を変更します。

スクリーンショット 2025-01-02 16.24.08.png

もう一度、localhost:3000にアクセスしてログインすると、アクセストークンの取得に成功し、ホーム画面にリダイレクトされます。

スクリーンショット 2025-01-02 16.38.20.png

ユーザーの確認

User Management > Users から実際に作成されたユーザーを確認することができます。

下書き一覧 (2)のコピー.tiff

Ktorでトークンの検証を行う

クライアント(React)でAuth0からアクセストークンを取得できました。

リソースサーバー(Ktor)は保護されているサーバーです。誰でもリソースにアクセスできるわけではありません。

アクセスを許可されている(認可されている)ことを証明するために、クライアントがリクエスト時に付与するものがアクセストークンです。

なので、リソースサーバーでは、リクエストを受けた時に、このアクセストークンが正しいものであるか検証する必要があります。

ReactからKtorにリクエストする

ReactのExtarnal API > Ping API ボタンを押すと、RESULT にJsonが表示されます。しかし、これはモックなので、Ktorのレスポンスを表示します。

スクリーンショット 2025-01-04 9.57.42.png

まず、Ktorに簡単なAPIを1つ作成しておきます。

エンドポイント:http://localhost:8080/health
レスポンス:{"msg":"OK!"} ※ モックと同じJosn構造です。

Application.kt
fun Application.module() {

    routing {
        get("health") {
            call.respondText("""{"msg":"OK!"}""", ContentType.Application.Json)
        }
    }
}

ReactのExtarnalAPI.jscallApi関数でKtorのエンドポイントを指定します。

ExtarnalAPI.js
const callApi = async () => {
     .....
-    const response = await fetch(`${apiOrigin}/api/external`, {
+    const response = await fetch("http://localhost:8080/health", {
         headers: {
           Authorization: `Bearer ${token}`,
         },
     });
     .....
}

もう一度、Ping API ボタンを押すと、{"msg":"OK!"}というJsonがKtorから返却されました。

※ もし新しいJsonが画面に反映されない場合、画面をリロードしてから、Ping API ボタンを押してみてください。

スクリーンショット 2025-01-04 10.08.11.png

Ktorでトークン検証の仕組みを作る

KtorのApplication.ktApplication.module関数内に以下のコードを記述します。
issueraudiencemyRealm${...}は置き換えてください。

Application.kt
import com.auth0.jwk.JwkProviderBuilder
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*

fun Application.module() {

    val issuer = "https://${Auth0で作成したTenantの名称}.jp.auth0.com/"
    val audience = "${Auth0で作成したAPIのIdentifier}"
    val myRealm = "${このリソースサーバーを示す名称}"

    val jwkProvider = JwkProviderBuilder(issuer).build()

    install(Authentication) {
        jwt("auth0") {
            realm = myRealm
            verifier(jwkProvider, issuer)
            validate { credential ->
                val containsAudience = credential.payload.audience.contains(audience)
                if (containsAudience) JWTPrincipal(credential.payload) else null
            }
        }
    }
}

エンドポイントを保護するには以下のようにコードを追記します。

Application.kt
fun Application.module() {

    routing {
+        authenticate("auth0") {
             get("health") {
                 call.respondText("""{"msg":"OK!"}""", ContentType.Application.Json)
             }
+        }
    }
}

ブラウザからhttp://localhost:8080/healthにリクエストすると、401 Unauthorizedが返却され、エンドポイントが保護されているのが確認できます。

スクリーンショット 2025-01-04 9.54.00.png

しかし、同じエンドポイントにReactからリクエストすると、正常にレスポンスが返却されます。

これは、Reactからリクエストするとき、Authorizationヘッダーにアクセストークンが付与されているからです。Ktorでトークンの検証が行われ、認可されているユーザーと判断されました。

下書き一覧のコピー.tiff

試しに、トークンに適当な文字列を追加してみると、Ktorからは401 Unauthorizedが返却されます。

ExtarnalAPI.js
const callApi = async () => {
     .....
     const response = await fetch("http://localhost:8080/health", {
         headers: {
-          Authorization: `Bearer ${token}`,
+          Authorization: `Bearer ${token}abc`,
         },
     });
     .....
}

下書き一覧 (1)のコピー.tiff

参考

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?