Реализация входа через социальные сети для 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. Следующие шаги — зарегистрировать пользователя и сохранить информацию о пользователе в базе данных или в том, что вам подходит.
Приложение выглядит примерно так:
Заключение
Вход через социальные сети — это функция, предлагающая множество преимуществ как пользователям, так и разработчикам. Его легко интегрировать, и он может улучшить взаимодействие с пользователем и безопасность приложения или веб-сайта. Поэтому рекомендуется использовать его в своих проектах.
Также опубликовано здесь.
Оригинал