4

我一直在使用 Django Rest Framework 开发具有自定义后端的 Next.js 应用程序,主要关注社交平台(Google、Github 等)的身份验证。这是我想要使用的流程:

  1. 让 NextAuth 为社会认证做繁重的工作。例如,当用户想要使用他/她的 Google 帐户登录时,它会返回一个访问令牌和一个 ID 令牌。
  2. 将 Google 返回的 id 令牌和访问令牌放入 NextAuth 会话对象中。
  3. 在前端,使用会话对象中的这两个令牌向 DRF 后端发出 POST 请求,该后端实质上接受访问令牌和 id 令牌并返回访问令牌和刷新令牌。注意。DRF 后端具有处理社交身份验证的设置dj-rest-authdjango-allauth
  4. DRF 后端以 HTTPOnly cookie 的形式发回令牌。所以,下次我想向 DRF API 发出请求时,cookie 应该随请求一起传递。

这是正确和安全的,还是我在踢自己的脚?

我的上下文代码:

index.tsx

import React, { useEffect } from "react";
import { signIn, signOut, useSession } from "next-auth/client";
import { Typography, Button, Box } from "@material-ui/core";
import { makeUrl, BASE_URL, SOCIAL_LOGIN_ENDPOINT } from "../urls";
import axios from "axios";
axios.defaults.withCredentials = true;

function auth() {
  const [session, loading] = useSession();

  useEffect(() => {
    const getTokenFromServer = async () => {
      // TODO: handle error when the access token expires
      const response = await axios.post(
        // DRF backend endpoint, api/social/google/ for example
        // this returns accessToken and refresh_token in the form of HTTPOnly cookies
        makeUrl(BASE_URL, SOCIAL_LOGIN_ENDPOINT, session.provider),
        {
          access_token: session.accessToken,
          id_token: session.idToken,
        },
      );
    };

    if (session) {
      getTokenFromServer();
    }
  }, [session]);

  return (
    <React.Fragment>
      <Box
        display="flex"
        justifyContent="center"
        alignItems="center"
        m={5}
        p={5}
        flexDirection="column"
      >
        {!loading && !session && (
          <React.Fragment>
            <Typography variant="button">Not logged in</Typography>
            <Button
              variant="outlined"
              color="secondary"
              onClick={() => signIn()}
            >
              Login
            </Button>
          </React.Fragment>
        )}
        {!loading && session && (
          <React.Fragment>
            <Typography>Logged in as {session.user.email}</Typography>
            <pre>{JSON.stringify(session, null, 2)}</pre>
            <Button
              variant="outlined"
              color="primary"
              onClick={() => signOut()}
            >
              Sign Out
            </Button>
          </React.Fragment>
        )}
      </Box>
    </React.Fragment>
  );
}

export default auth;

api/auth/[...nextauth].ts

import NextAuth from "next-auth";
import { InitOptions } from "next-auth";
import Providers from "next-auth/providers";
import { NextApiRequest, NextApiResponse } from "next";
import axios from "axios";

import { BASE_URL, SOCIAL_LOGIN_ENDPOINT, makeUrl } from "../../../urls";
import { AuthenticatedUser, CustomSessionObject } from "../../../types";
import { GenericObject } from "next-auth/_utils";

const settings: InitOptions = {
  providers: [
    Providers.Google({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      authorizationUrl:
        "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
    }),
  ],

  secret: process.env.NEXT_AUTH_SECRET,

  session: {
    maxAge: 6 * 60 * 60, // 6 hours
  },

  callbacks: {
    async signIn(user: AuthenticatedUser, account, profile) {
      if (account.provider === "google") {
        const { accessToken, idToken, provider } = account;
        user.accessToken = accessToken;
        user.idToken = idToken;
        user.provider = provider;
        return true;
      }

      return false;
    },

    async session(session: CustomSessionObject, user: AuthenticatedUser) {
      session.accessToken = user.accessToken;
      session.idToken = user.idToken;
      session.provider = user.provider;
      return session;
    },

    async jwt(token, user: AuthenticatedUser, account, profile, isNewUser) {
      if (user) {
        token.accessToken = user.accessToken;
        token.idToken = user.idToken;
        token.provider = user.provider;
      }

      return token;
    },
  },
};

export default (req: NextApiRequest, res: NextApiResponse) => {
  return NextAuth(req, res, settings);
};

我对会话对象是否足够安全以存储令牌这一事实感到压力。我还想实现一种机制,在访问令牌过期时使用刷新令牌刷新访问令牌。

4

1 回答 1

6

随着时间的推移,我最终解决了这个问题。我写了一篇由两部分组成的文章,概述了我是如何解决它的,可以在这里这里找到

于 2021-04-30T09:02:31.217 回答