पूर्वापेक्षाएँ
इस ट्यूटोरियल का पालन करने के लिए आपको आवश्यकता होगी:
- एक 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) और पासवर्ड (password) पास करते हुए एक POST HTTP रिक्वेस्ट करें जैसे निम्न उदाहरण में (Postman का उपयोग करके):
Redis डेटाबेस सेटअप करना
सबसे पहले, आपको Upstash Redis पर साइन-अप करना होगा (टेस्टिंग उद्देश्यों के लिए एक मुफ़्त योजना काम करेगी), एक बार हो जाने पर कंसोल में लॉग-इन करें और आप एक नया डेटाबेस बना सकते हैं:
"Create database" पर क्लिक करके आगे बढ़ें, इसे MovieManager नाम दें, और इसे Global के रूप में सेट करें। अब हम Upstash CLI का उपयोग करके कुछ डमी डेटा जोड़ते हैं
हम 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 के साथ एन्क्रिप्टेड है (सादे टेक्स्ट में यह password है), आमतौर पर, यूज़र जिन्हें API तक पहुंच की आवश्यकता होती है वे एक वेबसाइट के माध्यम से रजिस्टर करते हैं (या एक वेबसाइट में अपने क्रेडेंशियल लेते हैं), इस उदाहरण में हम एक रजिस्टर एंडपॉइंट प्रदान नहीं करते हैं
Upstash CLI में दर्ज हर कमांड को आपको OK उत्तर देना चाहिए, यदि सब कुछ सही है और आप Data Browser पर जाते हैं और Hash चुनते हैं, तो आपके द्वारा डाले गए डेटा की सूची होगी
प्राधिकरण वर्कफ़्लो
जैसा कि पहले उल्लेख किया गया है, हमारे एंडपॉइंट सार्वजनिक रूप से सुलभ नहीं हैं, इसलिए हमें यूज़र्स को प्रमाणित और अधिकृत करने का एक तरीका चाहिए। प्रमाणीकरण के लिए हम एक लॉगिन एंडपॉइंट प्रदान करते हैं; प्राधिकरण के लिए, संरक्षित एंडपॉइंट के लिए एक ऑथोराइजेशन हेडर की आवश्यकता होती है, जिसे रिक्वेस्ट के साथ भेजा जाना चाहिए। यह विस्तार से है कि वर्कफ़्लो कैसे काम करता है:
- यूज़र साइन-इन एंडपॉइंट का अनुरोध करता है, उपयोगकर्ता नाम और पासवर्ड पोस्ट करता है
- सर्वर यूज़र को प्रमाणित करने का प्रयास करता है, यदि यूज़र वैध है तो सर्वर एक JWT (JSON Web Token) और एक रिफ्रेश टोकन बनाता है और वापस भेजता है, रिफ्रेश टोकन को हमारे Upstash Redis इंस्टेंस पर भी स्टोर किया जाता है
- क्लाइंट टोकन वापस प्राप्त करता है और उन्हें कहीं स्टोर करता है (यह क्लाइंट की जिम्मेदारी है कि कैसे/कहां स्टोर करना है)
- क्लाइंट एक संरक्षित एंडपॉइंट का अनुरोध करता है, हेडर में JWT भेजता है
- सर्वर JWT प्राप्त करता है, इसे सत्यापित करता है, और यदि यह सत्यापित है तो क्लाइंट द्वारा अनुरोधित डेटा वापस भेजता है
- एक बार JWT समाप्त हो जाने या समाप्त होने के करीब होने पर, क्लाइंट री-लॉगिन किए बिना एक विशिष्ट एंडपॉइंट पर रिफ्रेश टोकन भेजकर एक नया JWT अनुरोध कर सकता है।
- सर्वर रिफ्रेश टोकन प्राप्त करता है, इसे सत्यापित करता है, और यदि सत्यापन सकारात्मक है तो एक नया JWT और रिफ्रेश टोकन जारी करता है, उन्हें क्लाइंट को वापस भेजता है, और नया रिफ्रेश टोकन फिर से स्टोर करता है
JWT और रिफ्रेश टोकन एक ही प्रारूप में हैं, लगभग समान जानकारी रखते हैं लेकिन दो अलग-अलग कुंजियों का उपयोग करते हैं (हम अपने .env फ़ाइल में सेट करेंगे) और दो अलग-अलग समाप्ति: JWT के लिए एक छोटी (चूंकि यह सत्र के दौरान सबसे अधिक उपयोग किया जाने वाला टोकन है, हम इसे जल्द ही समाप्त कर देते हैं, यदि यह इंटरसेप्ट हो जाता है) और रिफ्रेश टोकन के लिए एक लंबी। दोनों की अवधि इस बात पर निर्भर करती है कि आपको कितना सुरक्षित रहने की आवश्यकता है; आमतौर पर, JWT एक घंटे से कम में समाप्त होता है, और रिफ्रेश टोकन एक महीने तक चल सकता है। यदि दोनों टोकन समाप्त हो जाते हैं, तो यूज़र को फिर से लॉग इन करने की आवश्यकता होती है।
प्रोजेक्ट सेटअप करना
एक बार जब हम 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 बिट से हेक्स स्ट्रिंग का उपयोग कर सकते हैं। अपने Upstash कंसोल, डिटेल्स टैब, REST API सेक्शन से UPSTASH_REDIS_REST_URL और UPSTASH_REDIS_REST_TOKEN प्राप्त करें:
अब हम अपने एंडपॉइंट्स को तैयार करना शुरू कर सकते हैं:
POST /auth/signin यह यूज़र को लॉगिन करता है, ईमेल और पासवर्ड की आवश्यकता होती है जिसे JSON ऑब्जेक्ट {"email":"email", "password": "password"} के रूप में पास किया जाता है, यह यूज़र की जानकारी, एक JWT, और एक रिफ्रेश टोकन के साथ एक JSON ऑब्जेक्ट लौटाता है।
GET /movies/ मूवीज़ की सूची को JSON ऑब्जेक्ट के रूप में लौटाता है, इसके लिए एक वैध JWT की आवश्यकता होती है जिसे इस प्रारूप में हेडर में पास किया जाता है: Authorization: Bearer xxx
GET /movies/$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");
}
}
};
हमारा साइन-इन एंडपॉइंट केवल दो पैरामीटर के साथ POST स्वीकार करेगा: user और password। सबसे पहले, हम जांचते हैं कि यूज़र हमारे Redis डेटाबेस में मौजूद है या नहीं:
const user = await redis.hgetall(`user:${req.body.user}`);
यदि यूज़र मौजूद है तो हम एन्क्रिप्टेड पासवर्ड की तुलना करते हैं:
const validPassword = bcrypt.compare(req.body.password, user.password);
इस बिंदु पर, यदि पासवर्ड मेल खाता है, तो हम मान सकते हैं कि यूज़र प्रमाणित है और हम एक JWT और एक रिफ्रेश टोकन वापस भेज सकते हैं, हम रिफ्रेश टोकन को अपने Redis इंस्टेंस में भी स्टोर करते हैं। ऐसा करने के लिए हम utils.js नामक एक बाहरी फ़ाइल में कुछ फ़ंक्शन का उपयोग करते हैं
यह क्लाइंट की जिम्मेदारी है कि वापस किए गए टोकन को स्टोर करे, जब आवश्यक हो तो प्राधिकरण के लिए उनका उपयोग करे, और समाप्त होने पर उन्हें रीफ्रेश करे
हमारे पास अपना टोकन जेनरेट करने के लिए एक फ़ंक्शन generateAccessToken है, एक हमारे रिफ्रेश टोकन को जेनरेट करने के लिए generateRefreshToken, और एक हमारे 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) का उपयोग कर सकते हैं, https://localhost:3000/api/auth/signin पर पोस्ट करके और उपयोगकर्ता नाम (me@home.org) और पासवर्ड (password) पास करके, आपको यूज़र डिटेल्स के साथ एक JSON ऑब्जेक्ट वापस मिलना चाहिए साथ ही एक JWT और एक रिफ्रेश टोकन:
यदि सब कुछ सही था, तो आपके Redis डेटाबेस में आपको नए बनाए गए रिफ्रेश टोकन के लिए एक नई हैश एंट्री देखनी चाहिए:
अब, हम टोकन रीफ्रेश रूट 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);
}
};
यह utils.js से tokenRefresh फ़ंक्शन का उपयोग करता है यह सत्यापित करके शुरू होता है कि टोकन वैध है और डीकोड किया जा सकता है, फिर यह Redis पर जांचता है कि यूज़र के पास एक रिफ्रेश टोकन है (वह जिसे हमने पहले addToList के साथ स्टोर किया था), यदि सब कुछ सही है तो यह एक नया JWT, एक नया रिफ्रेश टोकन जेनरेट करता है (और इसे फिर से Redis में स्टोर करता है) और सब कुछ वापस क्लाइंट को भेजता है।
हम अपने टूल का उपयोग करके इस एंडपॉइंट का परीक्षण कर सकते हैं, https://localhost:3000/api/auth/refresh पर पोस्ट करके और पैरामीटर के रूप में रिफ्रेश टोकन पास करके:
अब, हमारा काल्पनिक क्लाइंट लॉग इन करने और अपने टोकन को रीफ्रेश करने में सक्षम है, आइए देखें कि टोकन का उपयोग प्रमाणित अनुरोध करने के लिए कैसे किया जा सकता है।
एक नया 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");
}
}
}
};
हमारे utils.js से verifyToken फ़ंक्शन का उपयोग करके हम अपने API एंडपॉइंट तक पहुंच को केवल उन यूज़र्स तक सीमित कर सकते हैं जो एक वैध टोकन प्रदान करते हैं। हमने कुछ नमूना क्वेरी कीं, पहली मूवीज़ की सूची प्राप्त करने के लिए
const result = await redis.scan(0, { match: 'movie:*' });
और दूसरी URL रिक्वेस्ट में id पैरामीटर के आधार पर एक मूवी का विवरण प्राप्त करने के लिए:
const result = await redis.hgetall(id);
दोनों रिक्वेस्ट यूज़र स्टेटस पर निर्भर हैं जिसे verifyToken के माध्यम से जांचा जाता है लेकिन आप मिक्स एंड मैच कर सकते हैं, उदाहरण के लिए, सूची सार्वजनिक हो सकती है और विवरण संरक्षित हो सकते हैं। चूंकि हमारे पास यूज़र में स्टोर एक लेवल भी है (और टोकन में) हम अधिक प्राधिकरण लेवल रख सकते हैं। आइए मूवीज़ की सूची प्राप्त करने का प्रयास करें:
और एक मूवी का विवरण:
क्लाइंट परिप्रेक्ष्य
जैसा कि हमने पहले कहा, हमने केवल सर्वर भाग पर ध्यान केंद्रित किया, यह एक API का मुख्य दायरा है, यह अमूर्त होना चाहिए, यह एक वेबसाइट नहीं है। क्लाइंट डेटा का अनुरोध कैसे करता है (प्रोग्रामिंग लैंग्वेज, लाइब्रेरी, आदि) क्लाइंट डेवलपर की पसंद है, हम एंडपॉइंट्स की एक सूची प्रदान करते हैं, हमारे एंडपॉइंट्स क्या उम्मीद कर रहे हैं, और हमारे एंडपॉइंट्स क्लाइंट को क्या लौटाते हैं। डेटा की सभी हैंडलिंग, रीफ्रेश देरी, आदि क्लाइंट रणनीति हैं।
आगे क्या
यह एक संरक्षित API के वर्कफ़्लो का सिर्फ एक बुनियादी उदाहरण है। यहां से इसमें केवल सुधार किया जा सकता है: Redis पर डेटा कैसे स्टोर किया जाता है इसे अनुकूलित करना, यूज़र डेटा को दूसरे Redis इंस्टेंस पर स्टोर करके लॉगिन सुरक्षा में सुधार करना, सभी से पहले यूज़र द्वारा भेजे गए डेटा की जांच और सत्यापन करना, अधिक एंडपॉइंट जोड़ना, GraphQL प्रारूप में डेटा लौटाना, अपने API के लिए एक क्लाइंट बनाना, प्रति घंटे अधिकतम कॉल की संख्या तक पहुंच सीमित करना, एक्सेस सीमित करने के लिए लेवल का उपयोग करना... एक्सटेंशन और सुधार अनंत हैं!