Реализация входа через социальные сети для React и бэкэнд-приложений

Реализация входа через социальные сети для React и бэкэнд-приложений

7 февраля 2023 г.

Как программисты, нам, вероятно, когда-то приходилось реализовывать вход через социальные сети, и эта функция становится все более распространенной в современных приложениях и на веб-сайтах.

Социальный вход позволяет пользователям входить в приложения или веб-сайты, используя свои учетные записи, такие как Facebook, Google, Twitter и т. д. Это экономит время пользователей, поскольку им не нужно создавать дополнительную учетную запись и пароль, а также используются меры безопасности социальной сети.

Например, если пользователь включил двухэтапную аутентификацию в своей учетной записи Google, эта безопасность также будет применяться к используемому приложению или веб-сайту.

В этом руководстве мы реализуем Github и Google авторизуемся в приложении React с помощью TypeScript. Мы будем использовать Vite для создания нашего приложения и pnpm в качестве менеджера пакетов.

Вход с помощью RRSS: первые шаги

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

Для GiHub выполните следующие действия.< /сильный>

Для Google выполните следующие действия

Обязательно храните ключи в надежном месте.

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

Итак, у нас будет два приложения, серверное и клиентское, каждое в своем каталоге.

Настройка серверной части

Мы создаем наше приложение с помощью следующей команды:

pnpm init -y

Устанавливаем зависимости, которые нам понадобятся в проекте:

pnpm install express axios cors dotenv tsc ts-node
pnpm install @types/cors @types/express @types/node ts-node-dev typescript -D

После этого создаем следующую структуру проекта:

├── src/
   ├── routes/
      ├── github-routes.ts
      └── google-routes.ts
   ├── controllers/
      ├── github-controller.ts
      └── google-controller.ts
   └── server.ts
├── .env
├── .env-example

Настройка клиента

Устанавливаем зависимости, которые нам понадобятся в проекте:

pnpm install @octokit/auth @react-oauth/google @nextui-org/react axios react-router-dom

После этого создаем следующую структуру проекта:

├── src/
   ├── assets/
      ├── icons/
         ├── Github.tsx
         ├── Google.tsx
         ├── Logout.tsx
         └── index.ts
   ├── pages/
      ├── home/
         ├── services/
            └── home-services.ts
         └── HomePage.tsx
      └── login/
          └── LoginPage.tsx
   ├── index.tsx
   ├── App.tsx
   └── main.tsx
├── .env
├── .env-example

Вход на GitHub

Бэкенд

  • Файл server.ts:

import express from 'express';
import cors from 'cors';

import githubRoutes from './routes/github-routes';
import googleRoutes from './routes/google-routes';

const PORT = process.env.PORT || 3001

const app = express();

app.use(
  cors({
    origin: ['http://localhost:5173'],
    methods: 'GET,POST',
  }),
);

app.use(express.json());

app.use('/api/github', githubRoutes);
app.use('/api/google', googleRoutes);

app.listen(PORT, () => console.log('Server on port', PORT));

Этот код создает серверное приложение с помощью Express. Приложение использует библиотеку cors для включения доступа между источниками (CORS) и устанавливает источник и разрешенные методы.

Приложение также использует функцию app.use(), чтобы включить использование JSON в запросах и определить маршруты для службы GitHub и службы Google.

Наконец, приложение прослушивает порт, указанный в константе PORT, или порт 3001, если порт не указан.

* Файл github-routes.ts:

import express, { Request, Response, Router } from 'express';
import { getAccessToken, getUserData } from '../controllers/github-controller';

const router: Router = express.Router();

router.get('/accessToken', (req: Request, res: Response) => {
  const code = req.query.code;
  getAccessToken(code as string).then((resp) => res.json(resp));
});

router.get('/userData', (req: Request, res: Response) => {
  const accessToken = req.query.accessToken;
  getUserData(accessToken as string).then((resp) => res.json(resp));
});

export default router;

Этот код создает маршрутизатор Express, который определяет два маршрута: /accessToken и /userData.

Маршрут /accessToken — это маршрут GET, который принимает параметр code из запроса и вызывает функцию getAccessToken службы GitHub.

Маршрут /userData — это маршрут GET, который берет параметр accessToken из запроса и вызывает функцию getUserData службы GitHub.

* Файл google-routes.ts:

import express, { Request, Response, Router } from 'express';
import { getUserData } from '../controllers/google-controller';

const router: Router = express.Router();

router.get('/userData', (req: Request, res: Response) => {
  const accessToken = req.query.accessToken;
  getUserData(accessToken as string).then((resp) => res.json(resp));
});

export default router;

Этот код создает маршрутизатор Express, определяющий маршрут GET с именем /userData. Маршрут получает параметр accessToken из запроса и вызывает функцию getUserData службы Google.

* Файл github-controller.ts:

import * as dotenv from 'dotenv';
import axios from 'axios';

dotenv.config();

type AccessTokenData = {
  access_token: string;
  token_type: string;
  scope: string;
} | null;

export const getAccessToken = async (
  code: string,
): Promise<AccessTokenData> => {
  try {
    const params = `?client_id=${process.env.GITHUB_CLIENT_ID}&client_secret=${process.env.GITHUB_CLIENT_SECRET}&code=${code}`;

    const { data } = await axios.post(
      `https://github.com/login/oauth/access_token${params}`,
      {},
      {
        headers: {
          Accept: 'application/json',
        },
      },
    );

    return data;
  } catch (error) {
    console.log(error);
    return null;
  }
};

export const getUserData = async (accessToken: string) => {
  try {
    const { data } = await axios.get('https://api.github.com/user', {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });

    return data;
  } catch (error) {
    return null;
  }
};

Функция getAccessToken получает параметр code и отправляет POST-запрос к GitHub API для получения токена доступа, используя предоставленные client_id, client_secret икод.

Функция getUserData получает параметр accessToken и возвращает информацию о пользователе из GitHub API, используя токен доступа, предоставленный в запросе.

* Файл google-controller.ts:

import axios from 'axios';

export const getUserData = async (accessToken: string) => {
  try {
    const { data } = await axios.get(
      'https://www.googleapis.com/oauth2/v3/userinfo',
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      },
    );

    return data;
  } catch (error) {
    return null;
  }
};

Функция getUserData получает параметр accessToken и выполняет запрос GET к API информации о пользователе Google OAuth, используя токен доступа, указанный в заголовке авторизации запроса, и возвращает информацию о пользователе. .

Теперь у нас уже есть серверная часть с необходимым минимумом, теперь давайте перейдем к интерфейсу.

Клиент

  • Файл main.tsx:

import { GoogleOAuthProvider } from "@react-oauth/google"

import ReactDOM from "react-dom/client"

import { NextUIProvider } from "@nextui-org/react"
import { darkTheme } from "./themes/darktheme"

const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID

import App from "./App"

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
 <NextUIProvider theme={darkTheme}>
  <GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
   <App />
  </GoogleOAuthProvider>
 </NextUIProvider>
)

В этом приложении мы будем использовать Next UI для стилей, вы можете использовать любую библиотеку пользовательского интерфейса, которую вы хотите.

Компонент NextUIProvider обеспечивает темную тему для всех компонентов приложения.

Компонент GoogleOAuthProvider обеспечивает вход Google в приложение с помощью предоставленного clientId.

* Компонент App.tsx:

import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { LoginPage, HomePage } from './pages';

const App = () => {
  return (
    <Router>
      <Routes>
        <Route path="/home" element={<HomePage />}></Route>
        <Route path="/" element={<LoginPage />}></Route>
      </Routes>
    </Router>
  );
};

export default App;

В компоненте приложения мы создадим маршруты страниц, которые будем использовать, и назначим соответствующий компонент.

* Компонент LoginPage.tsx

import { useGoogleLogin } from "@react-oauth/google"
import { useNavigate } from "react-router-dom"

import { Card, Spacer, Button, Text, Container } from "@nextui-org/react"

import { IconGitHub, IconGoogle } from "../../assets/icons"

const GITHUB_CLIENT_ID = import.meta.env.VITE_GITHUB_CLIENT_ID

const LoginPage = () => {
 const navigate = useNavigate()

 const loginToGithub = () => {
  localStorage.setItem("loginWith", "GitHub")
  window.location.assign(`https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}`)
 }

 const loginToGoogle = useGoogleLogin({
  onSuccess: tokenResponse => {
   localStorage.setItem("loginWith", "Google")
   localStorage.setItem("accessToken", tokenResponse.access_token)
   navigate("/home")
  },
 })

 return (
  <Container display='flex' alignItems='center' justify='center' css={{ minHeight: "100vh" }}>
   <Card css={{ mw: "420px", p: "20px" }}>
    <Text
     size={24}
     weight='bold'
     css={{
      as: "center",
      mb: "20px",
     }}
    >
     Login with
    </Text>
    <Spacer y={1} />
    <Button color='gradient' auto ghost onClick={() => loginToGithub()}>
     <IconGitHub />
     <Spacer x={0.5} />
     GitHub
    </Button>
    <Spacer y={1} />

    <Button color='gradient' auto ghost onClick={() => loginToGoogle()}>
     <IconGoogle />
     <Spacer x={0.5} />
     Google
    </Button>
   </Card>
  </Container>
 )
}

export default LoginPage

Когда пользователь нажимает кнопку входа в GitHub, он перенаправляется на страницу входа в GitHub, а в локальном хранилище браузера сохраняется ключ, указывающий, что пользователь входит в систему с помощью GitHub.

Когда пользователь нажимает кнопку входа в Google, функция useGoogleLogin элемента @react-oauth/google используется для входа в Google. Если вход выполнен успешно, в локальном хранилище браузера сохраняется ключ, указывающий, что пользователь входит в систему с помощью Google.

* Компонент HomePage.tsx:

import { useEffect, useRef, useState } from "react"
import { useNavigate } from "react-router-dom"
import { Button, Col, Container, Navbar, Row, Text, User } from "@nextui-org/react"

import { getAccessTokenGithub, getUserDataGithub, getUserDataGoogle } from "./services/home-services"

import { LogOutIcon } from "../../assets/icons"

interface UserDataGithub {
 avatar_url: string
 login: string
 bio: string
}

interface UserdataGoogle {
 name: string
 picture: string
 email: string
}

const HomePage = () => {
 const [userDataGithub, setUserDataGithub] = useState<null | UserDataGithub>(null)
 const [userDataGoogle, setUserDataGoogle] = useState<null | UserdataGoogle>(null)

 const loginWith = useRef(localStorage.getItem("loginWith"))

 const navigate = useNavigate()

 useEffect(() => {
  const queryString = window.location.search
  const urlParams = new URLSearchParams(queryString)
  const codeParam = urlParams.get("code")

  const accessToken = localStorage.getItem("accessToken")

  if (codeParam && !accessToken && loginWith.current === "GitHub") {
   getAccessTokenGithub(codeParam).then(resp => {
    localStorage.setItem("accessToken", resp.access_token)
    getUserDataGithub(resp.access_token).then((resp: UserDataGithub) => {
     setUserDataGithub(resp)
    })
   })
  } else if (codeParam && accessToken && loginWith.current === "GitHub") {
   getUserDataGithub(accessToken).then((resp: UserDataGithub) => {
    localStorage.setItem("accessToken", accessToken)
    setUserDataGithub(resp)
   })
  }
 }, [loginWith])

 useEffect(() => {
  const accessToken = localStorage.getItem("accessToken")

  if (accessToken && loginWith.current === "Google") {
   getUserDataGoogle(accessToken).then(resp => {
    setUserDataGoogle(resp)
   })
  }
 }, [loginWith])

 const setLogOut = () => {
  localStorage.removeItem("accessToken")
  localStorage.removeItem("loginWith")
  navigate("/")
 }

 if (!userDataGithub && !userDataGoogle) return null

 return (
  <>
   <Navbar isBordered variant='sticky'>
    <Navbar.Brand>
     <User
      bordered
      color='primary'
      size='lg'
      src={loginWith.current === "GitHub" ? userDataGithub?.avatar_url : userDataGoogle?.picture}
      name={loginWith.current === "GitHub" ? userDataGithub?.login : userDataGoogle?.name}
      description={loginWith.current === "GitHub" ? userDataGithub?.bio : userDataGoogle?.email}
     />
    </Navbar.Brand>
    <Navbar.Content>
     <Navbar.Item>
      <Button
       auto
       flat
       size='sm'
       icon={<LogOutIcon fill='currentColor' />}
       color='primary'
       onClick={() => setLogOut()}
      >
       Log out
      </Button>
     </Navbar.Item>
    </Navbar.Content>
   </Navbar>
   <Container gap={0}>
    <Row gap={1}>
     <Col>
      <Text h2>Login with {loginWith.current}</Text>
     </Col>
    </Row>
   </Container>
  </>
 )
}

export default HomePage

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

Сначала мы получаем код авторизации из URL-адреса страницы и, если пользователь вошел в систему с помощью GitHub, мы получаем токен доступа, а затем получаем данные пользователя из GitHub. Если пользователь вошел в систему с помощью Google, мы просто получаем данные пользователя Google.

Кроме того, у нас есть кнопка «Выход», которая удаляет токен доступа и логин из локального хранилища и перенаправляет пользователя на домашнюю страницу приложения.

Вот и все!

Теперь мы можем войти в GitHub или Google. Следующие шаги — зарегистрировать пользователя и сохранить информацию о пользователе в базе данных или в том, что вам подходит.

Приложение выглядит примерно так:

Login

Login with Github

Login with Google

Репозиторий здесь.

Заключение

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


Также опубликовано здесь.


Оригинал