Авторизуйтесь с помощью GitHub

GitHub предлагает бесплатный API для аутентификации пользователей. Он основан на OAuth, открытом стандарте аутентификации. OAuth — довольно обширная тема, но наш случай использования не так уж сложен. Вы можете узнать больше в документации GitHub, но вот как это работает по сути:

  1. Мы создадим приложение GitHub и введем в него URL обратного вызова. Мы получим идентификатор клиента и секрет клиента (это просто строки случайного вида).
  2. Мы добавим ссылку «Войти с GitHub» на нашу страницу. Ссылка будет указывать на URL GitHub, который будет включать наш ID клиента и случайную строку, которую мы сгенерируем (называемую «state») в параметрах запроса.
  3. GitHub покажет пользователю страницу в зависимости от статуса аутентификации:
    • Пользователю будет показана страница входа в GitHub (только если он еще не вошел в систему).
    • Пользователя спросят, хочет ли он авторизоваться в нашем приложении (только если он еще не авторизовался в нашем приложении).
    • Если пользователь согласится (или недавно авторизовался), GitHub перенаправит его на URL обратного вызова, который мы определили в шаге 1.
  4. Перенаправление будет включать код и состояние, которое мы отправили в шаге 2, в качестве параметров запроса. Если состояние не совпадет со случайной строкой, которую мы отправили, мы поймем, что происходит что-то подозрительное, и прервем процесс. В противном случае мы отправим POST-запрос на https://github.com/login/oauth/access_token вместе с нашим идентификатором клиента, секретом клиента и кодом, который мы получили в качестве параметра запроса. Если все пройдет успешно, GitHub ответит нам маркером доступа.
  5. Мы будем использовать маркер доступа в заголовке Authorization каждый раз, когда захотим получить данные профиля пользователя от GitHub.

У нас есть план. Давайте начнем.

Создайте приложение GitHub

Перейдите в GitHub Developer Settings, слева нажмите OAuth Apps, а затем нажмите кнопку «New OAuth app». Он задаст вам несколько вопросов. Введите http://localhost:5173 для URL домашней страницы и http://localhost:5173/login для URL обратного вызова, а остальное заполните по своему усмотрению. Мы указываем адреса localhost, потому что нам нужно протестировать наше приложение перед развертыванием на его окончательный URL. Вы можете просто обновить URL при развертывании или создать новое приложение, а это оставить для тестирования и разработки.

После отправки формы вы окажетесь на странице, где будет указан идентификатор клиента вашего приложения. Вы также увидите кнопку «Сгенерировать новый клиентский секрет». Сгенерируйте его и скопируйте оба в новый файл в корневом каталоге вашего репозитория и сохраните его с именем .env. Он должен выглядеть следующим образом:

GITHUB_CLIENT_ID=<your client ID>
GITHUB_CLIENT_SECRET=<your client secret>
Вход в полноэкранный режим Выход из полноэкранного режима

Хорошей практикой является сохранение секретов и конфигурации нашего приложения в переменных окружения. Теперь добавьте этот файл в файл .gitignore, чтобы случайно не опубликовать свой секрет на GitHub. Чтобы загрузить его в окружение во время разработки, мы установим пакет dotenv:

npm install -D dotenv
Вход в полноэкранный режим Выйти из полноэкранного режима

Затем мы импортируем его и вызовем в нашем vite.config.ts. В итоге файл будет выглядеть следующим образом:

import { defineConfig } from "vite";
import rakkas from "rakkasjs/vite-plugin";
import tsconfigPaths from "vite-tsconfig-paths";

dotenv.config();

export default defineConfig({
    envDir: ".",
    plugins: [
        tsconfigPaths(),
        rakkas({
            adapter: "cloudflare-workers",
        }),
    ],
});
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь мы сможем обращаться к переменным, например, process.env.GITHUB_CLIENT_ID в нашем коде на стороне сервера. process.env — это глобал, специфичный для Node, но Rakkas делает его доступным и для Cloudflare Workers.

Добавление ссылки «Войти»

Сейчас у нас есть только одна страница. Но так будет не всегда. Вероятно, мы хотим видеть ссылку «Войти» в заголовке каждой страницы. В Rakkas есть система макетов для таких общих элементов. Макеты оборачивают вложенные макеты и страницы в одном каталоге и его подкаталогах. Так, если мы создадим файл layout.tsx в директории src/routes, он обернет все страницы нашего приложения.

Мы сказали, что ссылка «Войти» будет указывать на URL-адрес GitHub. Этот URL, согласно документации GitHub, имеет вид https://github.com/login/oauth/authorize?client_id=<CLIENT_ID>${clientId}&state=<STATE>. ID нашего клиента находится в process.env.GITHUB_CLIENT_ID, который доступен только на стороне сервера. Поэтому мы снова используем useServerSideQuery для доступа к нему. Обработкой параметра state мы займемся позже, а пока присвоим ему 12345. Итак, вот первый проект нашего src/routes/layout.tsx:

import { LayoutProps, useServerSideQuery } from "rakkasjs";

export default function MainLayout({ children }: LayoutProps) {
    const {
        data: { clientId, state },
    } = useServerSideQuery(() => ({
        clientId: process.env.GITHUB_CLIENT_ID,
        state: "12345",
    }));

    return (
        <>
            <header>
                <strong>uBlog</strong>
                <a
                    style={{ float: "right" }}
                    href={
                        "https://github.com/login/oauth/authorize" +
                        `?client_id=${clientId}` +
                        `&state=${state}`
                    }
                >
                    Sign in with GitGub
                </a>
                <hr />
            </header>
            {children}
        </>
    );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Когда вы запустите сервер dev, вы увидите, что теперь у нас есть заголовок сайта. А ссылка «Sign in with GitHub» приведет вас на страницу авторизации GitHub. Если вы авторизуете свое приложение, GitHub перенаправит вас на URL, который выглядит как http://localhost:5173/login?code=<BUNCH_OF_RANDOM_LOOKING_CHARS>&state=12345. http://localhost:5173/login — это URL, который мы ввели в качестве URL обратного вызова, а остальные — это параметры, отправленные GitHub. Конечно, вы получите ошибку 404, потому что мы еще не реализовали эту конечную точку. Давайте сделаем это сейчас.

Обратный вызов логина

Мы создадим файл src/routes/login.page.tsx для реализации обратного вызова входа. В нем мы будем использовать параметр запроса code для получения маркера доступа от GitHub, а затем использовать этот маркер доступа для получения данных профиля пользователя. Мы снова будем использовать хук useServerSideQuery, потому что не хотим раскрывать наш клиентский секрет клиенту. Помните, что обратный вызов useServerSideQuery выполняется на сервере и не будет частью клиентского пакета. Давайте сначала посмотрим, как выглядят данные профиля, распечатав их в формате JSON:

import { PageProps, useServerSideQuery } from "rakkasjs";

export default function LoginPage({ url }: PageProps) {
    const error = url.searchParams.get("error");
    const code = url.searchParams.get("code");
    const state = url.searchParams.get("state");

    const { data: userData } = useServerSideQuery(async () => {
        if (code && state === "12345") {
            const { access_token: token } = await fetch(
                "https://github.com/login/oauth/access_token" +
                    `?client_id=${process.env.GITHUB_CLIENT_ID}` +
                    `&client_secret=${process.env.GITHUB_CLIENT_SECRET}` +
                    `&code=${code}`,
                {
                    method: "POST",
                    headers: { Accept: "application/json" },
                }
            ).then((r) => r.json<{ access_token: string }>());

            if (token) {
                const userData = fetch("https://api.github.com/user", {
                    headers: {
                        Authorization: `token ${token}`,
                    },
                }).then((r) => r.json());

                return userData;
            }
        }
    });

    if (error) {
        return <div>Error: {error}</div>;
    }

    return <pre>{JSON.stringify(userData, null, 2)}</pre>;
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Если все идет хорошо, вы должны увидеть данные профиля пользователя GitHub в формате JSON при нажатии на кнопку «Войти с GitHub». Мои данные выглядят следующим образом:

{
    "login": "cyco130",
    "id": 10846005,
    "node_id": "MDQ6VXNlcjEwODQ2MDA1",
    "avatar_url": "https://avatars.githubusercontent.com/u/10846005?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/cyco130",
    "html_url": "https://github.com/cyco130",
    "followers_url": "https://api.github.com/users/cyco130/followers",
    "following_url": "https://api.github.com/users/cyco130/following{/other_user}",
    "gists_url": "https://api.github.com/users/cyco130/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/cyco130/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/cyco130/subscriptions",
    "organizations_url": "https://api.github.com/users/cyco130/orgs",
    "repos_url": "https://api.github.com/users/cyco130/repos",
    "events_url": "https://api.github.com/users/cyco130/events{/privacy}",
    "received_events_url": "https://api.github.com/users/cyco130/received_events",
    "type": "User",
    "site_admin": false,
    "name": "Fatih Aygün",
    "company": "Lityum AŞ",
    "blog": "",
    "location": "Istanbul",
    "email": null,
    "hireable": null,
    "bio": "Programmer, musician, amateur linguist.",
    "twitter_username": "cyco130",
    "public_repos": 32,
    "public_gists": 4,
    "followers": 26,
    "following": 25,
    "created_at": "2015-02-04T09:24:28Z",
    "updated_at": "2022-06-29T03:02:45Z"
}
Войти в полноэкранный режим Выход из полноэкранного режима

Успехов! Мы многого добились! Самое время сделать перерыв!

Что дальше?

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

Прогресс до этого момента вы можете найти на GitHub.

Оцените статью
devanswers.ru
Добавить комментарий