ورود اجتماعی با Cognito و NextAuth

استخرهای کاربر AWS Cognito به شما امکان می دهد برنامه های خود را در اکوسیستم AWS مدیریت کنید. این همه ویژگی های اساسی که از یک سیستم احراز هویت انتظار دارید را ارائه می دهد.
وقتی برنامهای را روی زیرساخت AWS میسازم، به دلیل ادغام یکپارچه آن با سایر سرویسهای AWS مانند مجوز API Gateway، ترجیح میدهم از استخرهای کاربر Cognito استفاده کنم.
استخرهای کاربر Cognito همچنین به شما این امکان را می دهد که ورود اجتماعی را برای کاربران خود تنظیم کنید. در دنیایی که اکثر مردم به احراز هویت از طریق حساب های موجود مانند گوگل، فیس بوک، اپل و غیره عادت کرده اند، بسیار راحت است.
در این مقاله، نحوه ادغام لاگین اجتماعی در برنامه های Next.js خود را از طریق Cognito پوشش خواهم داد. ما اساساً به کاربران خود اجازه میدهیم با استفاده از حسابهای اجتماعی خود در حالی که از Cognito به عنوان یک سرور تأیید اعتبار پروکسی استفاده میکنند، وارد سیستم شوند.
ما این کار را در حالی انجام خواهیم داد که رابط کاربری میزبان Cognito را دور بزنیم تا یک تجربه احراز هویت یکپارچه برای کاربر داشته باشیم.
استفاده از این رویکرد به جای مدیریت دستی ورود به سیستم اجتماعی، چند مزیت دارد:
-
ما می توانیم از استخرهای کاربران Cognito و همه ویژگی های غنی آن استفاده کنیم.
-
در برنامه Next.js ما فقط باید با 1 ارائه دهنده تأیید اعتبار، Cognito سر و کار داشته باشیم.
-
ما این آزادی را داریم که دکمههای ورود به سیستم اجتماعی خود را همانطور که میخواهیم طراحی کنیم، بدون اینکه توسط رابط کاربری میزبانی شده Cognito در بند قرار بگیریم.
-
لازم نیست خودمان پایگاه داده کاربران را مدیریت کنیم.
پیش نیازها
این مقاله به روند راه اندازی استخر کاربران Cognito شما نمی پردازد. قبل از اجرای مراحل این مقاله، مطمئن شوید که:
-
استخر کاربران Cognito.
-
یک سرویس گیرنده برنامه که برای مجموعه کاربران شما تنظیم شده است.
-
راه اندازی ورود به سیستم اجتماعی برای مشتری برنامه شما. شما نیازی به راه اندازی همه ارائه دهندگان اجتماعی ندارید، فقط یکی از آنها کافی است. همیشه می توانید ارائه دهندگان بیشتری را بعداً وصل کنید.
-
دامنه ای برای مجموعه کاربران شما. نیازی نیست که دامنه سفارشی باشد. دامنه پیش فرض ارائه شده توسط aws کافی است.
ساختار auth ما تقریباً باید شبیه این باشد:
راه اندازی NextAuth
در برنامه Next.js ابتدا کتابخانه NextAuth را نصب کنید.
npm install next-auth
سپس، تنظیمات NextAuth را در آن تنظیم کنید /api/auth/[...nextauth].ts
.
import { TokenSet } from "next-auth";
import NextAuth from "next-auth/next";
import { Provider } from "next-auth/providers";
const {
NEXTAUTH_URL,
COGNITO_REGION,
COGNITO_DOMAIN,
COGNITO_CLIENT_ID,
COGNITO_USER_POOL_ID,
COGNITO_CLIENT_SECRET,
} = process.env;
type TProvider = "Amazon" | "Apple" | "Facebook" | "Google";
function getProvider(provider: TProvider): Provider {
/*
Provider generation function to avoid repeating ourselved when
declaring providers in the authOptions below.
*/
return {
// e.g. cognito_google | cognito_facebook
id: `cognito_${provider.toLowerCase()}`,
// e.g. CognitoGoogle | CognitoFacebook
name: `Cognito${provider}`,
type: "oauth",
// The id of the app client configured in the user pool.
clientId: COGNITO_CLIENT_ID,
// The app client secret.
clientSecret: COGNITO_CLIENT_SECRET,
wellKnown: `https://cognito-idp.${COGNITO_REGION}.amazonaws.com/${COGNITO_USER_POOL_ID}/.well-known/openid-configuration`,
// Authorization endpoint configuration
authorization: {
url: `${COGNITO_DOMAIN}/oauth2/authorize`,
params: {
response_type: "code",
client_id: COGNITO_CLIENT_ID,
identity_provider: provider,
redirect_uri: `${NEXTAUTH_URL}/api/auth/callback/cognito_${provider.toLowerCase()}`
}
},
// Token endpoint configuration
token: {
url: `${COGNITO_DOMAIN}/oauth2/token`,
params: {
grant_type: "authorization_code",
client_id: COGNITO_CLIENT_ID,
client_secret: COGNITO_CLIENT_SECRET,
redirect_uri: `${NEXTAUTH_URL}/api/auth/callback/cognito_${provider.toLowerCase()}`
}
},
// userInfo endpoint configuration
userinfo: {
url: `${COGNITO_DOMAIN}/oauth2/userInfo`,
},
// Profile to return after successcul auth.
// You can do some transformation on your profile object here.
profile: function(profile) {
return {
id: profile.sub,
...profile
}
}
}
}
export const authOptions = {
/*
Generate the providers for each of our social login.
Adding a new OIDC provider is a simple as adding the name of the provider as displayed in the Cognito user pool to the TProvider type
and to this array.
*/
providers: [
...["Amazon", "Apple", "Facebook", "Google"].map((provider: TProvider) => getProvider(provider)),
],
callbacks: {
async signIn({ user, account, profile }) {
// Return true to allow sign in and false to block sign in.
return true;
},
async redirect({ url, baseUrl }){
// Return the url to redirect to after successful sign in.
return baseUrl;
},
async jwt({ token, account, profile, user }){
// Retrieve jwt tokens
if (account) {
// account is provided upon the inital auth
return {
...token,
accessToken: account.access_token,
idToken: account.id_token,
}
}
},
async session({ session, token }) {
/*
Forward tokens to client in case you need to make authorized
API calls to an AWS service directly from the front end.
*/
session.accessToken = token.accessToken;
session.idToken = token.idToken;
return session;
}
}
};
export default NextAuth(authOptions);
همانطور که می بینید، ما یک جریان اولیه OAuth2.0 را پیکربندی می کنیم. در اینجا می توانید اطلاعات بیشتری در مورد نقاط پایانی OIDC Cognito بخوانید. این جریان با هر ارائه دهنده OIDC که در مجموعه کاربری خود پیکربندی می کنید کار می کند.
بهبود پاسخ به تماس
در حال حاضر سناریویی وجود دارد که ما به آن رسیدگی نمی کنیم. هنگام احراز هویت از این طریق، Cognito یک نشانه تازهسازی طولانی مدت را برمیگرداند. زمانی که توکنهای گفته شده منقضی شدهاند، از نشانههای تازهسازی برای درخواست توکنهای دسترسی و شناسه جدید استفاده میشود.
این به ما این امکان را میدهد تا کاربر را برای مدت طولانی بدون اینکه مجبور کنیم هر بار که توکنهایش منقضی میشود به سیستم وارد شود، نگه داریم. ما فقط زمانی از کاربر می خواهیم که دوباره وارد سیستم شود که خود توکن refresh منقضی شده باشد یا به روش دیگری باطل شود.
برای پشتیبانی از این قابلیت، باید کد موجود در آن را به روز کنیم jwt
و session
توابع پاسخ به تماس
ابتدا بیایید به روز رسانی کنیم jwt
عملکرد پاسخ به تماس:
async jwt({ token, account, profile, user }){
if (account) {
// This is an initial login, set JWT tokens.
return {
...token,
accessToken: account.access_token,
idToken: account.id_token,
refreshToken: account.refresh_token,
expiresAt: account.expires_at,
tokenType: 'Bearer'
}
}
if (Date.now() < token.expiresAt) {
// Access/Id token are still valid, return them as is.
return token;
}
// Access/Id tokens have expired, retrieve new tokens using the
// refresh token
try {
const response = await fetch(`${COGNITO_DOMAIN}/oauth2/token`, {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
client_id: COGNITO_CLIENT_ID,
client_secret: COGNITO_CLIENT_SECRET,
grant_type: "refresh_token",
refresh_token: token.refreshToken
}),
method: "POST"
})
const tokens: TokenSet = await response.json();
if (!response.ok) throw tokens;
return {
...token,
accessToken: tokens.access_token,
idToken: tokens.id_token,
expiresAt: Date.now() + (Number(tokens.expires_in) * 1000)
}
} catch (error) {
// Could not refresh tokens, return error
console.error("Error refreshing access and id tokens: ", error);
return { ...token, error: "RefreshTokensError" as const }
}
}
این عملکرد قبل از بازگرداندن توکنهای jwt در هنگام درخواست توکن به شرح زیر است:
-
اگر اولین درخواست است (یعنی اقدام اولیه ورود به سیستم)، اضافه کنید
accessToken
،idToken
،refreshToken
، وexpiresAt
به شی نشانه. راexpiresAt
ویژگی به ما اطلاع می دهد که نشانه دسترسی و شناسه چه زمانی منقضی می شود. شی ای که در اینجا برگردانده می شود با نشان داده می شودtoken
پارامتر در تمام درخواست های توکن بعدی. -
اگر این اولین درخواست نیست، بررسی کنید که آیا نشانه های دسترسی و شناسه با مقایسه زمان فعلی با
expiresAt
زمان در شی نشانه که در نقطه 1 تنظیم شده است. -
اگر توکن ها منقضی نشده اند، همان شی نشانه را برگردانید.
-
اگر توکنها منقضی شدهاند، با استفاده از نشانه تازهسازی، توکنهای جدید را واکشی کنید.
-
اگر واکشی ناموفق بود، خطا را پرتاب کنید. در بلوک catch، شی نشانه فعلی را برگردانید اما an را اضافه کنید
error
ویژگی تا بتوانیم آن را بررسی کرده و کاربر را به صفحه ورود هدایت کنیم. -
اگر واکشی موفقیت آمیز بود، آن را جایگزین کنید
accessToken
،idToken
وexpiresAt
خصوصیات در شی token و برگرداندن شی token جدید.
در مرحله بعد، ما باید آن را به روز کنیم session
پاسخ به تماس این فراخوانی به ما اجازه میدهد تا ویژگیهای جلسه را تنظیم کنیم که در قسمت جلویی از طریق useSession
قلاب.
تنها کاری که باید در اینجا انجام دهیم این است که آن را اضافه کنیم error
ویژگی توکن به شی جلسه. به این ترتیب میتوانیم خطا را در قسمت جلویی شناسایی کنیم و کاربر را به صفحه ورود هدایت کنیم.
async session({ session, token }) {
/*
Forward tokens to client in case you need to make authorized API
calls to an AWS service directly from the front end.
*/
session.accessToken = token.accessToken;
session.idToken = token.idToken;
/*
If there is an error when refreshing tokens, include it so it can
be forwarded to the front end.
*/
session.error = token.error;
return session;
}
در حال پیکربندی وضعیت جلسه مشترک
برای پیکربندی وضعیت جلسات، به روز رسانی کنید _app.tsx
یا _app.jsx
فایل با کد زیر:
import '../styles/globals.css';
import { SessionProvider } from "next-auth/react";
function MyApp({
Component,
pageProps: { session, ...pageProps }
}) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
)
}
export default MyApp
این تغییر به ما اجازه می دهد تا از جلسه در سراسر برنامه استفاده کنیم.
ورود به Frontend با ارائه دهندگان
برای اینکه به کاربر اجازه دهیم با ارائه دهنده دلخواه خود وارد شود، باید دکمه های ورود به سیستم را برای هر ارائه دهنده در مؤلفه صفحه ورود به سیستم خود پیکربندی کنیم.
import { useEffect, useState } from "react";
/* The getProviders function returns a promise that resolves
to an object containing all of the configured providers. */
// The signIn functions initiates the signIn process.
import { getProviders, signIn } from "next-auth/react";
export default function SignIn() {
const [providers, setProviders] = useState(null);
// Fetch providers on mount.
useEffect(() => {
getProviders().then((providers) => setProviders(providers));
}, []);
return (
<div>
{providers && (
<>
<div>
<button onClick={
() => signIn(providers["cognito_amazon"].id)
}>
Login with Amazon
</button>
</div>
<div>
<button onClick={
() => signIn(providers["cognito_apple"].id)
}>
Login with Apple
</button>
</div>
<div>
<button onClick={
() => signIn(providers["cognito_facebook"].id)
}>
Login with Facebook
</button>
</div>
<div>
<button onClick={
() => signIn(providers["cognito_google"].id)
}>
Login with Google
</button>
</div>
</>
)}
</div>
);
}
برای زمینه بیشتر، شی ارائه دهنده به شکل زیر خواهد بود:
{
<prodiver_id>: {
"id": <provider_id>
"name": <provider_name>
"type": <provider_type>
"signinUrl": <provider signin url>
"callbackUrl": <provider callback url>
}
}
در این پیکربندی، شی ارائه دهنده به شکل زیر خواهد بود:
{
"cognito_amazon":
{
"id":"cognito_amazon",
"name":"CognitoAmazon",
"type":"oauth",
"signinUrl":
"http://localhost:3000/api/auth/signin/cognito_amazon",
"callbackUrl":
"http://localhost:3000/api/auth/callback/cognito_amazon"
},
"cognito_apple":{
"id": "cognito_apple",
"name":"CognitoApple",
"type":"oauth",
"signinUrl":"http://localhost:3000/api/auth/signin/cognito_apple",
"callbackUrl":
"http://localhost:3000/api/auth/callback/cognito_apple"
},
"cognito_facebook":{
"id": "cognito_facebook",
"name":"CognitoFacebook",
"type":"oauth",
"signinUrl":
"http://localhost:3000/api/auth/signin/cognito_facebook",
"callbackUrl":
"http://localhost:3000/api/auth/callback/cognito_facebook"
},
"cognito_google":{
"id":"cognito_google",
"name":"CognitoGoogle",
"type":"oauth",
"signinUrl":
"http://localhost:3000/api/auth/signin/cognito_google",
"callbackUrl":
"http://localhost:3000/api/auth/callback/cognito_google"
}
}
Frontend useSession hook
برای دسترسی به داده های جلسه در قسمت جلویی، می توانیم از آن استفاده کنیم useSession
قلاب از next-auth/react
.
این قلاب به ما امکان دسترسی به داده های جلسه و وضعیت احراز هویت کاربر را می دهد.
ابتدا قلاب را وارد کنید.
import { useSession } from 'next-auth/react'
سپس در داخل کامپوننت خود، از آن به صورت زیر استفاده کنید:
const { data, status } = useSession();
را data
ویژگی شامل تمام داده هایی است که در آن تنظیم کرده ایم session
پاسخ تماسی که ما پیکربندی کردیم [...nextauth.ts]
. همچنین شامل موارد اضافی خواهد بود user
دارایی با اطلاعات مربوط به کاربر فعلی مانند آدرس ایمیل.
را status
ویژگی رشته ای است که حاوی اطلاعاتی درباره وضعیت احراز هویت کاربر است. مقادیر احتمالی آن “بارگیری”، “تأیید شده” یا “احراز هویت نشده” است. می توانید از این برای پیاده سازی مسیرهای محافظت شده یا تغییر مسیر به صفحه ورود به سیستم زمانی که یک جلسه منقضی شده است استفاده کنید.