Redis
 Computer >> コンピューター >  >> プログラミング >> Redis

Next.js ルートと Upstash Redis を使用して安全で高速な認証された API を作成する

この記事では、Next.js API ルートと Upstash Redis を活用して、最小限だが完全に機能する認証された Rest API サービスを構築します。これは、データ、ユーザー認証、および JWT 処理の両方のための超高速ストレージ/キャッシュ システムとして使用します。このプロジェクトにはフロントエンドはなく、さまざまなクライアントでクエリできる API のみが公開されることに注意してください。

前提条件

チュートリアルに従うには、次のものが必要です。

  • Upstash アカウント — ここから無料アカウントにサインアップしてください
  • Redis の基本的な知識
  • Next.js API ルートの基礎知識
  • 認証と認可のワークフローに関する基本的な知識
  • HTTP リクエストを行うために選択したツール

Upstash Redis とは

Upstash はサーバーレスのインメモリ クラウド データベースです。 Redis に基づいています。これを使用して、API によって提供されるデータを保存します。また、ユーザー ベースとユーザー トークンも Upstash Redis に保存します。

これから構築するもの

クライアント アプリケーションがデータ (この特定のケースでは映画のリスト) を要求できるようにする REST API サービスをコーディングします。 JWT を使用してエンドポイントを保護し、トークンを取得する API ログイン サービスをコーディングし、リフレッシュ トークン ワークフローも実装します。

集中力がなくなります (私たちは「偏見のない」サービスを構築しているため) クライアント開発についてはわかりませんが、誰でもクライアントを構築できるようにサービスの仕様を提供します。

リポジトリとデモ

手順を進めるために、プロジェクト リポジトリのクローンを作成することをお勧めします。

GitHub のソース

次の URL でデモを試すこともできます。

https://upstash-dwov9jbiq-popland.vercel.app/api/auth/signin

サービスに接続するには、ユーザー名 (me@home.org) を渡して POST HTTP リクエストを作成します。 ) とパスワード (パスワード) ) 次の例のように (Postman を使用):

Next.js ルートと Upstash Redis を使用して安全で高速な認証された API を作成する

Redis データベースのセットアップ

まず、Upstash Redis にサインアップする必要があります (テスト目的には無料プランが機能します)。コンソールにログインしたら、新しいデータベースを作成できます。

Next.js ルートと Upstash Redis を使用して安全で高速な認証された API を作成する

[データベースの作成] をクリックして続行し、MovieManager という名前を付け、グローバルに設定します。ここで、Upstash CLI を使用してダミー データを追加します。

Next.js ルートと Upstash Redis を使用して安全で高速な認証された API を作成する

HMSET コマンドを使用して、いくつかのムービーを Redis ハッシュ (基本的にはオブジェクト) として追加します。

 hmset movie:’Dr. Strangelove’ director ‘Stanley Kubrick’ year 1964
 hmset movie:’2001: A Space Odyssey’ director ‘Stanley Kubrick’ year 1968
 hmset movie:’Pulp Fiction’ director ‘Quentin Tarantino’ year 1994
 hmset movie:’Django Unchained’ director ‘Quentin Tarantino’ year 2012

データへのアクセスを許可されるユーザーも追加します。ユーザーは Redis ハッシュでもあります。

 hmset user:’me@home.org’ password $2b$10$zctxUVDyy3jzvSp68oKpMOnkyra4R.NzOFVh9aii3Y43X7XtetoyK level 0

ご注意ください :パスワードは bcrypt で暗号化されています (平たく言うと、 それはパスワードです )、通常、API にアクセスする必要があるユーザーは Web サイト経由で登録します (または Web サイトで資格情報を取得します)。この例では、登録エンドポイントは提供しません

すべてが正しくてデータ ブラウザに移動すると、Upstash CLI に入力されたすべてのコマンドで OK の応答が返されるはずです。 ハッシュを選択すると、挿入したデータのリストが表示されます。

Next.js ルートと Upstash Redis を使用して安全で高速な認証された API を作成する

認可ワークフロー

前に述べたように、私たちのエンドポイントは公的にアクセスできないため、ユーザーを認証および認可する方法が必要です。認証のために、ログイン エンドポイントが提供されます。認可のために、保護されたエンドポイントはリクエストとともに送信される認可ヘッダーを必要とします。ワークフローの詳細な動作は次のとおりです。

  • ユーザーは、ユーザー名とパスワードを投稿して、サインイン エンドポイントをリクエストします
  • サーバーはユーザーの認証を試みます。ユーザーが有効な場合、サーバーは JWT (JSON Web トークン) とリフレッシュ トークンを作成して送り返します。リフレッシュ トークンは Upstash Redis インスタンスにも保存されます
  • クライアントはトークンを取り戻し、どこかに保存します(トークンをどこにどのように保存するかはクライアントの責任です)
  • クライアントは保護されたエンドポイントをリクエストし、ヘッダーで JWT を送信します
  • サーバーは JWT を受信して検証し、検証された場合はクライアントが要求したデータを送り返します
  • JWT の有効期限が切れるか、期限が近づくと、クライアントは特定のエンドポイントにリフレッシュ トークンを送信することで、再ログインせずに新しい JWT をリクエストできます。
  • サーバーはリフレッシュ トークンを受信して検証し、検証が成功した場合は新しい JWT とリフレッシュ トークンを発行し、それらをクライアントに送り返し、新しいリフレッシュ トークンを再度保存します

JWT とリフレッシュ トークンは同じ形式で、ほぼ同じ情報を取得しますが、2 つの異なるキーを使用します (.env で設定します) ファイル) を作成し、2 つの異なる有効期限を取得しました。JWT 用の短い有効期限 (セッション中に最も使用されるトークンであるため、傍受された場合に備えてすぐに有効期限が切れるようにします) と、リフレッシュ トークン用の長い有効期限です。どちらの期間も、どの程度安全性を維持する必要があるかによって異なります。通常、JWT は 1 時間以内に期限切れになり、リフレッシュ トークンは 1 か月間持続します。両方のトークンの有効期限が切れた場合、ユーザーは再度ログインする必要があります。

プロジェクトのセットアップ

Upstash Redis データベースの使用が完了したら、プロジェクトの初期化を開始できます。まず、新しい Next.js プロジェクトを作成します。

 npx create-next-app upstash-jwt

次に、新しく作成したフォルダー upstash-jwt に入ります。 そして必要なモジュールをインストールします。

 npm i bcrypt jsonwebtoken @upstash/redis

.env.local を作成します ファイルにキーを保存し、正しいデータを入力します。

 SECRET_TOKEN=
 SECRET_RTOKEN=
 UPSTASH_REDIS_REST_URL=
 UPSTASH_REDIS_REST_TOKEN=

JWT の生成に使用される SECRET_TOKEN と SECRET_RTOKEN を作成します。これらのキーは秘密にしておく必要があり、非常にランダムで推測が困難であることに注意してください。64 ビットから 16 進数の文字列を使用できます。Upstash コンソールの詳細タブの REST API セクションから UPSTASH_REDIS_REST_URL と UPSTASH_REDIS_REST_TOKEN を取得します。

Next.js ルートと Upstash Redis を使用して安全で高速な認証された API を作成する

これで、エンドポイントのレイアウトを開始できます。

POST /auth/signin ユーザーをログインするには、電子メールとパスワードを JSON オブジェクト {"email":"email", "password": "password"} として渡す必要があります。 、ユーザーの情報、JWT、およびリフレッシュ トークンを含む JSON オブジェクトを返します。

/映画/を入手 映画のリストを JSON オブジェクトとして返します。これには、Authorization:Bearer xxx という形式でヘッダーに渡される有効な JWT が必要です。

/映画/$ID を取得 ID $ID の映画の詳細を返します

POST /auth/refresh 新しい JWT を生成して返します。リフレッシュ トークンは refreshToken として渡す必要があります。 パラメータ。

API ルートのコード

サインイン エンドポイントから始めて、ファイル pages/api/auth/signin.js を作成しましょう 次のように:

import bcrypt from "bcrypt";
 
import {
 addToList,
 generateAccessToken,
 generateRefreshToken,
 redis,
} from "../../../utils";
 
export default async (req, res) => {
 if (req.method === "GET") {
 res.status(405).send("Not Allowed");
 } else {
 console.log(req.body.user);
 try {
 const user = await redis.hgetall(`user:${req.body.user}`);
 if (user) {
 const validPassword = bcrypt.compare(req.body.password, user.password);
 if (validPassword) {
 const token = generateAccessToken(req.body.user, user.level);
 const refreshToken = generateRefreshToken(req.body.user, user.level);
 const refresh = await addToList(req.body.user, refreshToken);
 const content = {
 user: req.body.user,
 level: user.level,
 };
 res.status(200).json({
 message: "Logged in",
 content: content,
 JWT: token,
 refresh: refreshToken,
 });
 } else {
 res.status(400).json({ error: "Invalid Password" });
 }
 } else {
 res.status(401).json({ error: "User not found" });
 }
 } catch (error) {
 res.status(500).send("Internal Server Error");
 }
 }
};

サインイン エンドポイントは、user という 2 つのパラメーターを指定した POST のみを受け入れます。 とパスワード 。まず最初に、ユーザーが Redis データベースに存在するかどうかを次のように確認します。

 const user = await redis.hgetall(`user:${req.body.user}`);

ユーザーが存在する場合は、暗号化されたパスワードを比較します。

 const validPassword = bcrypt.compare(req.body.password, user.password);

この時点で、パスワードが一致する場合は、ユーザーが認証されているとみなして、JWT とリフレッシュ トークンを送り返すことができます。また、リフレッシュ トークンを Redis インスタンスに保存します。そのために、utils.js という外部ファイルにあるいくつかの関数を使用します。

返されたトークンを保存し、必要に応じて認証に使用し、期限切れになったら更新するのはクライアントの責任です。

トークン generateAccessToken を生成する関数があります。 1 つはリフレッシュ トークン generateRefreshToken を生成するためのものです。 1 つと、Redis addToList に更新トークンを保存するためのものです。 。この utils.js このファイルは、他のすべてのユーティリティ関数と参照 (Redis 接続、トークンの検証と更新など) を保持するためにも使用されます。

import { Redis } from "@upstash/redis";
import jwt from "jsonwebtoken";
 
export const redis = new Redis({
 url: process.env.UPSTASH_REDIS_REST_URL,
 token: process.env.UPSTASH_REDIS_REST_TOKEN,
});
export function generateAccessToken(username, email, level) {
 return jwt.sign(
 { user: username, email: email, level: level },
 process.env.SECRET_TOKEN,
 {
 expiresIn: "1h",
 },
 );
}
 
export function generateRefreshToken(username, email, level) {
 return jwt.sign(
 { user: username, email: email, level: level },
 process.env.SECRET_RTOKEN,
 {
 expiresIn: "30d",
 },
 );
}
 
export async function addToList(user, refresher) {
 try {
 await redis.hset("refresh:" + user, { refresh: refresher });
 } catch (error) {
 console.log(error);
 }
}
 
export async function tokenRefresh(refreshtoken, res) {
 var decoded = "";
 try {
 decoded = jwt.verify(refreshtoken, process.env.SECRET_RTOKEN);
 } catch (error) {
 return res.status(401).send("Can't refresh. Invalid Token");
 }
 if (decoded) {
 try {
 const rtoken = await redis.hget("refresh:" + decoded.user, "refresh");
 console.log(rtoken);
 if (rtoken !== refreshtoken) {
 return res.status(401).send("Can't refresh. Invalid Token");
 } else {
 const user = await redis.hgetall(`user:${decoded.user}`);
 console.log(user);
 const token = generateAccessToken(decoded.user, user.level);
 const refreshToken = generateRefreshToken(decoded.user, user.level);
 
 const refresh = await addToList(decoded.user, refreshToken);
 
 const content = {
 user: decoded.user,
 level: user.level,
 };
 return {
 message: "Token Refreshed",
 content: content,
 JWT: token,
 refresh: refreshToken,
 };
 }
 } catch (error) {
 console.log(error);
 }
 }
}
 
export async function verifyToken(token, res) {
 try {
 const decoded = jwt.verify(token, process.env.SECRET_TOKEN);
 return decoded;
 } catch (err) {
 return res.status(405).send("Token is invalid");
 }
}

これで、ツール (Postman など) を使用して、(http://localhost:3000/api/auth/signin に投稿することで署名プロセスをテストできるようになりました。 ユーザー名 (me@home.org) を渡します。 ) とパスワード (パスワード) )、JWT およびリフレッシュ トークンとともにユーザーの詳細を含む JSON オブジェクトを取得する必要があります。

Next.js ルートと Upstash Redis を使用して安全で高速な認証された API を作成する

すべてが正しければ、Redis データベースに、新しく作成されたリフレッシュ トークンの新しいハッシュ エントリが表示されるはずです。

Next.js ルートと Upstash Redis を使用して安全で高速な認証された API を作成する

次に、トークン リフレッシュ ルート refresh.js をコーディングして、認証プロセスを完了します。

import { redis, tokenRefresh } from "../../../utils";
 
export default async (req, res) => {
 if (req.method === "GET") {
 res.status(405).send("Not Allowed");
 } else {
 console.log(req.body.refresh);
 const refresp = await tokenRefresh(req.body.refresh, res);
 res.status(200).json(refresp);
 }
};

tokenRefresh を使用します。 utils.js からの関数 まず、トークンが有効でデコードできることを確認し、次にユーザーがリフレッシュ トークン (以前に addToList で保存したもの) を取得したかどうかを Redis でチェックします。 )、すべてが正しい場合は、新しい JWT と新しい更新トークンが生成され(そして Redis に再度保存され)、すべてがクライアントに送り返されます。

このエンドポイントは、ツールを使用して http://localhost:3000/api/auth/refresh に投稿してテストできます。 そして、リフレッシュ トークンをパラメータとして渡します。

Next.js ルートと Upstash Redis を使用して安全で高速な認証された API を作成する

これで、仮想のクライアントがログインしてトークンを更新できるようになりました。トークンを使用して認証されたリクエストを行う方法を見てみましょう。

新しい API ルートを作成します:api/movies/[[...id]].js これは、映画のリストを取得し、映画の詳細を取得するために使用されます。

import { redis, verifyToken } from "../../../utils";
 
export default async (req, res) => {
 var id;
 console.log(req.query);
 if (req.query.id) {
 id = req.query.id[0];
 }
 
 var decoded = "";
 const authHeader = req.headers["authorization"];
 const token = authHeader && authHeader.split(" ")[1];
 if (!token) {
 return res.status(403).send("A token is required for authentication");
 } else {
 decoded = await verifyToken(token, res);
 }
 if (decoded) {
 if (id) {
 try {
 const result = await redis.hgetall(id);
 console.log(result);
 return res.status(200).json(result);
 } catch (error) {
 return res.status(500).send("Internal Server Error");
 }
 } else {
 try {
 const result = await redis.scan(0, { match: "movie:*" });
 return res.status(200).json(result);
 } catch (error) {
 return res.status(500).send("Internal Server Error");
 }
 }
 }
};

verifyToken の使用 utils.js の関数 API エンドポイントへのアクセスを、有効なトークンを提供したユーザーのみに制限できます。いくつかのサンプル クエリを作成しました。最初のクエリは映画のリストを取得します

 const result = await redis.scan(0, { match: ‘movie:*’ });

2 つ目は、URL リクエストの ID パラメータに基づいて 1 つの映画の詳細を取得します。

const result = await redis.hgetall(id);

どちらのリクエストも、verifyToken によってチェックされるユーザー ステータスに依存します。 ただし、組み合わせて使用することもできます。たとえば、リストを公開し、詳細を保護することもできます。ユーザー (およびトークン) にもレベルが保存されているため、より多くの認証レベルを持つことができます。映画のリストを取得してみましょう:

Next.js ルートと Upstash Redis を使用して安全で高速な認証された API を作成する

および 1 つの映画の詳細:

Next.js ルートと Upstash Redis を使用して安全で高速な認証された API を作成する

クライアントの視点

前に述べたように、ここではサーバー部分に焦点を当てました。これは API の主要な範囲であり、抽象的である必要があり、Web サイトではありません。クライアントがデータをリクエストする方法 (プログラミング言語、ライブラリなど) はクライアント開発者の選択であり、エンドポイントのリスト、エンドポイントが期待しているもの、エンドポイントがクライアントに返すものを提供します。データの処理、更新の遅延などはすべてクライアントの戦略です。

次は何ですか

これは、保護された API のワークフローの基本的な例にすぎません。ここからは改善するしかありません。Redis でのデータの保存方法の最適化、別の Redis インスタンスにユーザー データを保存することによるログイン セキュリティの向上、ユーザーが送信するデータの最初のチェックと検証、エンドポイントの追加、GraphQL 形式でのデータの返し、API のクライアントの構築、1 時間あたりの最大呼び出し数へのアクセスの制限、アクセスを制限するレベルの使用…拡張と改善は無限です。


  1. Redis ZINCRBY –Redisでソートされた設定値の要素のスコアをインクリメントする方法

    このチュートリアルでは、redisデータストアのキーに保存されている並べ替えられた設定値の要素のスコアをインクリメントする方法について学習します。このために、コマンドを使用します– ZINCRBY redis-cliで。 このコマンドは、キーに保存されている並べ替えられた設定値の要素のスコアを指定された値だけインクリメントするために使用されます(インクリメント )。指定された要素が並べ替えられた設定値に存在しない場合は、指定された値で追加されます(増分 )そのスコアとして。キーが存在しない場合は、指定された要素を唯一のメンバーとして、新しいソート済みセットが作成されます。 増分

  2. GoogleCloudPlatformでのRedisEnterpriseのフルマネージドサービスがデリーで利用可能になりました

    Google CloudPlatformのフルマネージドRedisEnterpriseサービスが、インドのムンバイ(Asia-South1)地域に加えて、デリー地域(Asia-South2)でも利用できるようになったことをお知らせします。現在、顧客がGCPMarketplaceを介してRedisEnterpriseをデプロイし、リアルタイムのデータユースケースをサポートできる20を超えるGoogleCloudPlatformリージョンがあります。それらには、エンタープライズキャッシュ、セッション管理、ゲームリーダーボード、不正検出、高速トランザクション、非同期通信などが含まれます。お客様の多く