React + Next JS - Защищенные маршруты - PullRequest
1 голос
/ 04 августа 2020

Цель: Я хочу перенаправить зарегистрированного пользователя на домашнюю страницу, если он / она пытается вручную go перейти на /auth/signin.

Страница / компонент входа:

const Signin = ({ currentUser }) => {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const { doRequest, errors } = useRequest({
        url: '/api/users/signin',
        method: 'post',
        body: {
            email, password
        },
        onSuccess: () => Router.push('/')
    });

    useEffect(() => {
        const loggedUser = () => {
            if (currentUser) {
                Router.push('/');
            }
        };
        loggedUser();
    }, []);

Пользовательский компонент _app:

const AppComponent = ({ Component, pageProps, currentUser }) => {
    return (
        <div>
            <Header currentUser={currentUser} />
            <Component {...pageProps} currentUser={currentUser} />
        </div>

    )
};

AppComponent.getInitialProps = async (appContext) => {
    const client = buildClient(appContext.ctx);
    const { data } = await client.get('/api/users/currentuser');
    let pageProps = {};
    if (appContext.Component.getInitialProps) {
        pageProps = await appContext.Component.getInitialProps(appContext.ctx);
    }
    return {
        pageProps,
        ...data
    }
};

export default AppComponent;

Проблема :

Я пробовал это, но это вызывает небольшую задержку, так как это не будет отображаться на стороне сервера. Под задержкой я имею в виду: он показывает страницу, которую я не хочу показывать в течение секунды или около того, прежде чем перенаправить.

Я мог бы использовать знак загрузки или несколько условий if else, но это было бы работой , что было бы наилучшим подходом / практикой для решения этой проблемы?

Другое решение, которое я придумал:

  • Я построил переадресацию переадресации:
import Router from 'next/router';
export default (ctx, target) => {
    if (ctx.res) {
        // server 
        ctx.res.writeHead(303, { Location: target });
        ctx.res.end();
    } else {
        // client
        Router.push(target);
    }
}
  • Затем я сделал 2 HOC (для вошедшего и вышедшего пользователя) для защищенных маршрутов:
import React from 'react';
import redirect from './redirect';
const withAuth = (Component) => {
    return class AuthComponent extends React.Component {
        static async getInitialProps(ctx, { currentUser }) {
            if (!currentUser) {
                return redirect(ctx, "/");
            }
        }
        render() {
            return <Component {...this.props} />
        }
    }
}
export default withAuth;
  • Затем я обернул его компоненты для защиты страницы:
export default withAuth(NewTicket);

Есть ли лучший подход к этому? Был бы очень признателен за помощь.

Ответы [ 2 ]

2 голосов
/ 05 августа 2020

Ответ

Я действительно рекомендую посмотреть примеры, чтобы увидеть, как Next JS предлагает справиться с этим. Ресурсы действительно хороши!

https://github.com/vercel/next.js/tree/master/examples

Например, вы можете использовать next-auth, который является опцией авторизации с открытым исходным кодом.

Пример здесь. https://github.com/vercel/next.js/tree/master/examples/with-next-auth

// _app.js
import { Provider } from 'next-auth/client'
import '../styles.css'

const App = ({ Component, pageProps }) => {
  const { session } = pageProps
  return (
    <Provider options={{ site: process.env.SITE }} session={session}>
      <Component {...pageProps} />
    </Provider>
  )
}

export default App
// /pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'

const options = {
  site: process.env.VERCEL_URL,
  providers: [
    Providers.Email({
      // SMTP connection string or nodemailer configuration object https://nodemailer.com/
      server: process.env.EMAIL_SERVER,
      // Email services often only allow sending email from a valid/verified address
      from: process.env.EMAIL_FROM,
    }),
    // When configuring oAuth providers make sure you enabling requesting
    // permission to get the users email address (required to sign in)
    Providers.Google({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    }),
    Providers.Facebook({
      clientId: process.env.FACEBOOK_ID,
      clientSecret: process.env.FACEBOOK_SECRET,
    }),
    Providers.Twitter({
      clientId: process.env.TWITTER_ID,
      clientSecret: process.env.TWITTER_SECRET,
    }),
    Providers.GitHub({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
  ],
  // The 'database' option should be a connection string or TypeORM
  // configuration object https://typeorm.io/#/connection-options
  //
  // Notes:
  // * You need to install an appropriate node_module for your database!
  // * The email sign in provider requires a database but OAuth providers do not
  database: process.env.DATABASE_URL,

  session: {
    // Use JSON Web Tokens for session instead of database sessions.
    // This option can be used with or without a database for users/accounts.
    // Note: `jwt` is automatically set to `true` if no database is specified.
    // jwt: false,
    // Seconds - How long until an idle session expires and is no longer valid.
    // maxAge: 30 * 24 * 60 * 60, // 30 days
    // Seconds - Throttle how frequently to write to database to extend a session.
    // Use it to limit write operations. Set to 0 to always update the database.
    // Note: This option is ignored if using JSON Web Tokens
    // updateAge: 24 * 60 * 60, // 24 hours
    // Easily add custom properties to response from `/api/auth/session`.
    // Note: This should not return any sensitive information.
    /*
    get: async (session) => {
      session.customSessionProperty = "ABC123"
      return session
    }
    */
  },

  // JSON Web Token options
  jwt: {
    // secret: 'my-secret-123', // Recommended (but auto-generated if not specified)
    // Custom encode/decode functions for signing + encryption can be specified.
    // if you want to override what is in the JWT or how it is signed.
    // encode: async ({ secret, key, token, maxAge }) => {},
    // decode: async ({ secret, key, token, maxAge }) => {},
    // Easily add custom to the JWT. It is updated every time it is accessed.
    // This is encrypted and signed by default and may contain sensitive information
    // as long as a reasonable secret is defined.
    /*
    set: async (token) => {
      token.customJwtProperty = "ABC123"
      return token
    }
    */
  },

  // Control which users / accounts can sign in
  // You can use this option in conjunction with OAuth and JWT to control which
  // accounts can sign in without having to use a database.
  allowSignin: async (user, account) => {
    // Return true if user / account is allowed to sign in.
    // Return false to display an access denied message.
    return true
  },

  // You can define custom pages to override the built-in pages
  // The routes shown here are the default URLs that will be used.
  pages: {
    // signin: '/api/auth/signin',  // Displays signin buttons
    // signout: '/api/auth/signout', // Displays form with sign out button
    // error: '/api/auth/error', // Error code passed in query string as ?error=
    // verifyRequest: '/api/auth/verify-request', // Used for check email page
    // newUser: null // If set, new users will be directed here on first sign in
  },

  // Additional options
  // secret: 'abcdef123456789' // Recommended (but auto-generated if not specified)
  // debug: true, // Use this option to enable debug messages in the console
}

const Auth = (req, res) => NextAuth(req, res, options)

export default Auth

Таким образом, приведенный выше вариант - это defo приложение, отображаемое на стороне сервера, поскольку мы используем пути / api для auth. Если вам нужно бессерверное решение, вам, возможно, придется вытащить все из пути / api в лямбда (AWS Lambda) + api шлюза (AWS Api Gateway). Все, что вам нужно, это настраиваемый хук, который подключается к этому API. Конечно, вы можете сделать это по-разному.

Вот еще один пример аутентификации с использованием firebase.

https://github.com/vercel/next.js/tree/master/examples/with-firebase-authentication

И еще один пример с использованием Паспорт. js

https://github.com/vercel/next.js/tree/master/examples/with-passport

Также вы спрашивали о поведении загрузки, и этот пример может вам помочь

https://github.com/vercel/next.js/tree/master/examples/with-loading

?

Мнение

Пользовательский компонент _app обычно является оболочкой верхнего уровня (не совсем верхний _document под это описание).

Реально я бы создал компонент входа на один шаг ниже _app. Обычно я добиваюсь этого шаблона в компоненте Layout или, как в приведенных выше примерах, используя путь api или служебную функцию.

0 голосов
/ 04 августа 2020

Обновите следующее Js до 9.3+ и используйте getServerSideProps вместо getInitialProps. getServerSideProps работает только и всегда на стороне сервера, в отличие от getInitialProps. Перенаправить с getServerSideProps в случае сбоя аутентификации.

...