見出し画像

Auth0のIDトークンをサーバーで検証する

どうも。
Webアプリケーションにおいて、APIを使用してフロントエンドとバックエンド間でデータのやり取りをする場合についてお話しします。

認証されたユーザーがフロントエンドからバックエンドにデータをPOSTする場合、バックエンドのAPIに対してデータをリクエストすることになります。
この時、セキュリティの観点から、認証されたユーザーからのPOSTであるかどうかを必ず検証する必要があります。

よく使われるパターンは、リクエストのヘッダーのAuthorizationにIDトークンを含めて送信することです。
サーバーサイドでは、ヘッダーからIDトークンを取り出し、その有効性を検証する方法があります。

Auth0を使用する場合でも同様の手法が可能ですので、試してみたいと思います。

1.そもそもIDトークンってなに?

一般的にはJSON Webトークン(JWT)と呼ばれる仕組みを使用します。認証が行われると、名前やメールアドレスなどの個人のユーザー情報を取得することができます。IDトークンは、リソースサーバーやサーバーアプリケーションに対するユーザーの認証にも使用できます。

ここでAccessトークンとIDトークンの違いについて考えてみましょう。
TwitterやSpotifyなどの外部のWeb APIを使用する際には、常にAccessトークンを使用して認可が必要です。

● アクセストークン(認可)
Web APIを他のアプリケーションに公開する場合に使用します。

● IDトークン(認証)
Web APIを自分のアプリケーションの一部(バックエンドサービス)として作成し、保護する場合に使用します。

今回は、自身のバックエンドアプリケーションの認証にIDトークンを使用したいと思います。

それでは、まずは事前にAuth0のアカウントを取得し、簡単なログイン・ログアウト機能を実装しておきましょう。

● Fetchのタイミングで、IDトークンをヘッダーに含める。
● セッションからIDトークンを取得する。
● 認証サーバー(今回はAuth0)からPEMを取得する。
● JWTの検証を行い、IDトークンを検証する。

の4点です。

今回は、Next.jsを使用して進めていきます。
まず、必要なパッケージをインストールしましょう。

$ yarn add -D @auth0/nextjs-auth0

2.headersにIDトークンを入れる関数

// api/index.ts

const api = async (path: string, method: 'PATCH' | 'POST', token: string | undefined, body: { text: string }) => {
  const response = await fetch(path, {
  method: method,
    headers: {
      Authorization: token ?? '',
      'Content-Type''application/json'
    },
    body: JSON.stringify(body)
  });
  const res = (await response.json());
  return res;
};

Fetch時にトークンとボディを受け取り、PATCHまたはPOSTを行う処理を記述します。

3.IDトークンを取得する

pages/index.tsx

import { withPageAuthRequired, useUser } from '@auth0/nextjs-auth0';

// クライアントで取得する場合
const { user } = useUser();

// サーバー側で取得する場合
export const getServerSideProps: GetServerSidePropsContext = (context) => {
  const session = getSession(context.req, context.res);
  const user = this.session?.user;

  return { props: { token: user.idToken }}
}

ログイン処理を実装し、正しくログインできればデータの取得が可能になるはずです。
次にバックエンドのAPIを構築していきます。
今回はNext.jsのAPI Routesを使用します。
その前に、IDトークン(JWT)を検証するためのパッケージをインストールしましょう。

$ yarn add -D jsonwebtoken

このパッケージでは、IDトークンのペイロード内に含まれる署名を、Auth0が公開している公開鍵の内容と検証します。

4.pemを取得する。

やり方は3つあります。

●トークンをデコードして得られるkidを使用する方法:
トークンをデコードするとkidが取得できるため、kidを使用して公開鍵を生成することができます。

● Auth0から検証に使用する公開鍵を取得する方法:[your_account].auth0.com/pemにアクセスすると、pemファイルをダウンロードできますので、そのファイルをサーバーサイドに配置しておきます。

●JSON Web Key Set (JWKS)からpemを生成する方法:[your_account]/.well-known/jwks.jsonにGETリクエストを送信すると、keysの中に2つの公開鍵が含まれたJSONを取得できます。

以上が3つの方法ですが、今回は後者のJSON Web Key Set (JWKS) から pem を生成する方法を試してみたいと思います。

まず、以下のライブラリを事前にインストールしておきましょう。

$ yarn add -D jwk-to-pem

以下に、一連の処理の概要を書いてみます。

// jwk/index.ts

import jwkToPem, { JWK } from 'jwk-to-pem';

//----------------------------------------
// jwksのtype
//----------------------------------------
export type IJwks = {
  keys: {
    alg: string;
    kty: string;
    use: string;
    n: string;
    e: string;
    kid: string;
    x5t: string;
    x5c: [];
  }[];
};

//----------------------------------------
// jwksを取得する処理
//----------------------------------------
const jwks = async (path: string) => {
  const response = await fetch(path, {
    method: 'GET'
  });
  const jwk = (await response.json()) as IJwks;
  const pem = jwkToPem(jwk.keys[0as JWK); // 配列の1つ目を使用してpemに変換する
  return pem;
};

やっていることは簡単です。

● [your_account]/.well-known/jwks.json に対してGETリクエストを送信する。
● 取得したJSONデータはそのままでは公開鍵として使用できないため、pem形式に変換するためのライブラリを使用する。

jwksの取得からpemへの変換までが完了したため、バックエンドのAPI処理を記述していきます。

5.バックエンドでIDトークンを受け取って検証する

// pages/api/v1/index.ts

import type { NextApiRequest, NextApiResponse } from 'next';
import jwt from 'jsonwebtoken';
import { jwks } from 'services/jwks';

const Index = async (req: NextApiRequest, res: NextApiResponse) => {

  // 公開鍵を生成する
  const pem = await jwks('https://[your_account]/.well-known/jwks.json');
  const idToken = req.headers['authorization'];

  // idTokenがない場合はエラー
  if(!idToken) {
    res.status(400).json({
      datas: {
        error: {
          message'認証エラーです'
        }
      }
    });
  }

  // idTokenから署名で認証をチェック
  try {
    jwt.verify(String(req.headers['authorization']), pem, { algorithms: ['RS256'] }, function (_err, decodedToken) {
      if (_err) throw { _err };

      // DBにアクセスする処理を書いたり...
    });
  } catch (e) {
    return res.status(400).json({
      datas: {
        error: {
          message'ログインセッションが切れました。再度ログインしてください。'
        }
      }
    });
  }

export default Index;

証明書の内容を検証し、検証に成功した場合はdecodedTokenに認証情報が格納されます。
もしIDトークンの有効期限が切れた場合など、_errにエラー情報が格納されるため、再度ログインさせるなどの処理を実装しましょう。
認証情報が取得できれば、認証が正常に行われたことになりますので、APIサーバーやDBなどへのアクセスを行い、処理を実行しましょう。

IDトークンの有効期限はデフォルトで36000秒(10時間)です。


案外簡単に実装できたかと思います。
IDトークンの検証自体は初めて行う場合、全体像を把握するのが少し難しいかもしれませんが、実際に手を動かしてみるとそこまで難しくないですね。

この記事が参考になれば幸いです。それでは。

いいなと思ったら応援しよう!