برنامه نویسی

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

استخرهای کاربر AWS Cognito به شما امکان می دهد برنامه های خود را در اکوسیستم AWS مدیریت کنید. این همه ویژگی های اساسی که از یک سیستم احراز هویت انتظار دارید را ارائه می دهد.

وقتی برنامه‌ای را روی زیرساخت AWS می‌سازم، به دلیل ادغام یکپارچه آن با سایر سرویس‌های AWS مانند مجوز API Gateway، ترجیح می‌دهم از استخرهای کاربر Cognito استفاده کنم.

استخرهای کاربر Cognito همچنین به شما این امکان را می دهد که ورود اجتماعی را برای کاربران خود تنظیم کنید. در دنیایی که اکثر مردم به احراز هویت از طریق حساب های موجود مانند گوگل، فیس بوک، اپل و غیره عادت کرده اند، بسیار راحت است.

در این مقاله، نحوه ادغام لاگین اجتماعی در برنامه های Next.js خود را از طریق Cognito پوشش خواهم داد. ما اساساً به کاربران خود اجازه می‌دهیم با استفاده از حساب‌های اجتماعی خود در حالی که از Cognito به عنوان یک سرور تأیید اعتبار پروکسی استفاده می‌کنند، وارد سیستم شوند.

ما این کار را در حالی انجام خواهیم داد که رابط کاربری میزبان Cognito را دور بزنیم تا یک تجربه احراز هویت یکپارچه برای کاربر داشته باشیم.

استفاده از این رویکرد به جای مدیریت دستی ورود به سیستم اجتماعی، چند مزیت دارد:

  1. ما می توانیم از استخرهای کاربران Cognito و همه ویژگی های غنی آن استفاده کنیم.

  2. در برنامه Next.js ما فقط باید با 1 ارائه دهنده تأیید اعتبار، Cognito سر و کار داشته باشیم.

  3. ما این آزادی را داریم که دکمه‌های ورود به سیستم اجتماعی خود را همانطور که می‌خواهیم طراحی کنیم، بدون اینکه توسط رابط کاربری میزبانی شده Cognito در بند قرار بگیریم.

  4. لازم نیست خودمان پایگاه داده کاربران را مدیریت کنیم.

پیش نیازها

این مقاله به روند راه اندازی استخر کاربران Cognito شما نمی پردازد. قبل از اجرای مراحل این مقاله، مطمئن شوید که:

  1. استخر کاربران Cognito.

  2. یک سرویس گیرنده برنامه که برای مجموعه کاربران شما تنظیم شده است.

  3. راه اندازی ورود به سیستم اجتماعی برای مشتری برنامه شما. شما نیازی به راه اندازی همه ارائه دهندگان اجتماعی ندارید، فقط یکی از آنها کافی است. همیشه می توانید ارائه دهندگان بیشتری را بعداً وصل کنید.

  4. دامنه ای برای مجموعه کاربران شما. نیازی نیست که دامنه سفارشی باشد. دامنه پیش فرض ارائه شده توسط aws کافی است.

ساختار auth ما تقریباً باید شبیه این باشد:

158849cb ee26 4766 8523 7ccbd3f6445d

راه اندازی 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 در هنگام درخواست توکن به شرح زیر است:

  1. اگر اولین درخواست است (یعنی اقدام اولیه ورود به سیستم)، اضافه کنید accessToken، idToken، refreshToken، و expiresAt به شی نشانه. را expiresAt ویژگی به ما اطلاع می دهد که نشانه دسترسی و شناسه چه زمانی منقضی می شود. شی ای که در اینجا برگردانده می شود با نشان داده می شود token پارامتر در تمام درخواست های توکن بعدی.

  2. اگر این اولین درخواست نیست، بررسی کنید که آیا نشانه های دسترسی و شناسه با مقایسه زمان فعلی با expiresAt زمان در شی نشانه که در نقطه 1 تنظیم شده است.

  3. اگر توکن ها منقضی نشده اند، همان شی نشانه را برگردانید.

  4. اگر توکن‌ها منقضی شده‌اند، با استفاده از نشانه تازه‌سازی، توکن‌های جدید را واکشی کنید.

  5. اگر واکشی ناموفق بود، خطا را پرتاب کنید. در بلوک catch، شی نشانه فعلی را برگردانید اما an را اضافه کنید error ویژگی تا بتوانیم آن را بررسی کرده و کاربر را به صفحه ورود هدایت کنیم.

  6. اگر واکشی موفقیت آمیز بود، آن را جایگزین کنید 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 ویژگی رشته ای است که حاوی اطلاعاتی درباره وضعیت احراز هویت کاربر است. مقادیر احتمالی آن “بارگیری”، “تأیید شده” یا “احراز هویت نشده” است. می توانید از این برای پیاده سازی مسیرهای محافظت شده یا تغییر مسیر به صفحه ورود به سیستم زمانی که یک جلسه منقضی شده است استفاده کنید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا