На этой неделе мы рассмотрим использование социального входа Auth0 для аутентификации в GitHub. Мы также настраиваем веб-перехватчики для создания локального пользователя в нашей базе данных Prisma каждый раз, когда новый пользователь входит в систему Auth0.
Социальный вход с GitHub
Включение различных социальных провайдеров очень просто с Auth0. Следуйте этому руководству, чтобы настроить ряд социальных провайдеров — Google, Facebook, Twitter и т. д. Я только собираюсь настроить GitHub.
По умолчанию Auth0 имеет локальное имя пользователя и пароль. Отключите это, чтобы принудительно входить только с помощью социальных сетей.
Крючки Auth0
Мы собираемся настроить веб-перехватчик, который отправляет запрос одной из наших бессерверных функций каждый раз, когда новый пользователь входит в систему Auth0. Для этого мы можем создать правило в Auth0.
async function (user, context, callback) {
// do some stuff
callback(null, user, context);
}
Правила — это бессерверные функции, которые Auth0 будет вызывать каждый раз, когда пользователь входит в систему.
Auth0 сообщает нам, кто входит в систему, дает нам объект контекста с дополнительными данными и функцию обратного вызова, которую мы можем вызвать для продолжения процесса входа.
Первый параметр, ожидаемый обратным вызовом, — это ошибка. Если это значение равно null или не определено, процесс входа будет продолжен. Если этот параметр является истинным значением, он вызовет исключение и остановит процесс входа.
Если мы не вызовем функцию обратного вызова, процесс входа в систему в конечном итоге прервется.
Давайте настроим новый маршрут API в нашем приложении Next.js для обработки запроса от хука Auth0.
// pages/api/auth/hooks.js
module.exports = async (req, res) => {
const { email } = JSON.parse(req.body)
// create user in prisma
console.log('created user')
res.send({ received: true })
}
Нам нужно вызвать res.send, чтобы хук получил код состояния 200 и мог продолжить процесс входа.
Теперь давайте обновим наш хук Auth0, чтобы отправить запрос на нашу новую конечную точку. Мы предоставим адрес электронной почты пользователя в качестве тела нашего запроса.
async function (user, context, callback) {
await request.post('http://localhost:3000/api/auth/hooks', {
body: JSON.stringify({
email: user.email,
})
});
callback(null, user, context);
}
Крючки Auth0 похожи на точки с запятой — вы можете выбрать то, что предпочитаете в другом месте, но, вероятно, лучше поместить их здесь!
Теперь давайте активируем хук, войдя в наше приложение Next.js.
ОШИБКА!
Проблема в том, что этот хук Auth0 работает на каком-то удаленном сервере Auth0, а не на нашей локальной машине. Поэтому он понятия не имеет, что такое локальный хост. Нгрок на помощь!
Нгрок
Это инструмент, который перенаправляет общедоступный URL-адрес в Интернете на определенный порт, работающий на локальном хосте (наш сервер разработки Next.js). Это часто называют туннелированием.
Мы можем установить его с помощью npm.
npm i -g ngrok
А затем перенаправьте его на порт: 3000.
ngrok http 3000
Это должно дать вам URL-адрес, который вы можете использовать для замены «http://localhost:3000» в нашем запросе хука Auth0.
async function (user, context, callback) {
await request.post('https://0d4d01c96799.au.ngrok.io/api/auth/hooks', {
body: JSON.stringify({
email: user.email,
})
});
callback(null, user, context);
}
Теперь вы сможете инициировать запрос к нашему новому маршруту API, выполнив вход в приложение Next.js.
Не забудьте установить это на свой рабочий URL-адрес при развертывании приложения!
Вы должны увидеть этот выход «созданного пользователя» в консоль терминала, но мы пока этого не делаем. Давайте создадим нового пользователя в Prisma.
// pages/api/auth/hooks.js
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
module.exports = async (req, res) => {
const { email } = JSON.parse(req.body)
const user = await prisma.user.create({
data: { email },
})
await prisma.$disconnect()
console.log('created user')
res.send({ received: true })
}
Давайте завернем это в блок try, catch, чтобы, если нам не удастся создать пользователя, мы все равно отправили ответ на хук и не задержали процесс аутентификации.
// pages/api/auth/hooks.js
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
module.exports = async (req, res) => {
try {
const { email } = JSON.parse(req.body)
const user = await prisma.user.create({
data: { email },
})
console.log('created user')
} catch (err) {
console.log(err)
} finally {
await prisma.$disconnect()
res.send({ received: true })
}
}
Блок finally будет работать независимо от того, успешно ли мы создали пользователя или возникло исключение. Написание здесь логики очистки помогает СУШИТЬ наш код.
Теперь это должно создавать нового пользователя в Prisma каждый раз, когда пользователь входит в систему. Подождите, КАЖДЫЙ РАЗ?!?! это не хорошо!
Проблема 1: Новый пользователь при каждом входе в систему!
К счастью, мы ничего не подтолкнули к производству. Это могло стоить нам немного денег в приложении с высоким трафиком!
Мы хотим создать пользователя только при первом входе в систему, поэтому нам нужен какой-то способ узнать, успешно ли мы создали пользователя в прошлом. Мы могли бы открыть другой маршрут API для проверки связи с базой данных Prisma и убедиться, что пользователь с таким адресом электронной почты еще не существует, но для этого потребуется еще один переход от серверов Auth0 к Vercel. Мы не хотим заставлять нашего пользователя ждать без необходимости.
К счастью, Auth0 дает нам возможность устанавливать метаданные для нашего пользователя.
Мы можем установить метаданные после создания пользователя таким образом.
user.app_metadata = user.app_metadata || {};
user.app_metadata.localUserCreated = true;
Нам нужно вручную указать Auth0, чтобы он сохранял эти метаданные следующим образом.
await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);
И может прочитать метаданные, чтобы убедиться, что мы хотим создать такого пользователя.
if (!user.app_metadata.localUserCreated) {
// create prisma user
}
Полное правило должно выглядеть примерно так.
async function (user, context, callback) {
user.app_metadata = user.app_metadata || {};
if (!user.app_metadata.localUserCreated) {
await request.post('https://0d4d01c96799.au.ngrok.io/api/auth/hooks', {
body: JSON.stringify({
email: user.email,
})
});
user.app_metadata.localUserCreated = true;
await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);
}
callback(null, user, context);
}
Давайте также поместим это в блок try-catch, чтобы убедиться, что мы отвечаем, если возникает исключение.
async function (user, context, callback) {
try {
user.app_metadata = user.app_metadata || {};
if (!user.app_metadata.localUserCreated) {
await request.post('https://0d4d01c96799.au.ngrok.io/api/auth/hooks', {
body: JSON.stringify({
email: user.email,
})
});
user.app_metadata.localUserCreated = true;
await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);
}
callback(null, user, context);
} catch (err) {
callback(err);
}
}
Здорово! Итак, теперь каждый раз, когда пользователь входит в систему, а у нас нет учетной записи в prisma, он будет вызывать наш маршрут API для создания пользователя.
ЖДАТЬ! У нас просто есть открытый маршрут API, который будет создавать пользователя каждый раз, когда мы отправляем ему запрос?!? Это не хорошо! Откуда мы знаем, что это исходит от Auth0?!?
Проблема 2: Наш маршрут API для аутентификации не аутентифицирован!
Хорошо, есть несколько способов решить эту проблему. Вы можете подумать: «Разве не для этого у нас есть библиотека Auth0? Просто оберните его той функцией withApiAuthRequired, о которой вы бредили!»
Поскольку это исходит от Auth0, а не от нашего приложения Next.js, сеанс на самом деле не существует!
Нам нужно будет вручную отправить секретное значение из хука Auth0 и проверить его наличие и правильность в маршруте API. Это решение похоже на ключи API, которые сопоставляются с конкретным пользователем.
В меню «Правила» мы можем создать новый секрет.
Я рекомендую установить значение в длинную случайно сгенерированную строку.
Теперь мы можем получить доступ к этому значению в нашем хуке Auth0 следующим образом.
configuration.AUTH0_HOOK_SECRET
Давайте опубликуем это вместе с нашим запросом на маршрут API.
async function (user, context, callback) {
try {
user.app_metadata = user.app_metadata || {};
if (!user.app_metadata.localUserCreated) {
await request.post('https://0d4d01c96799.au.ngrok.io/api/auth/hooks', {
body: JSON.stringify({
email: user.email,
secret: configuration.AUTH0_HOOK_SECRET,
})
});
user.app_metadata.localUserCreated = true;
await auth0.users.updateAppMetadata(user.user_id, user.app_metadata);
}
callback(null, user, context);
} catch (err) {
callback(err);
}
}
Теперь нам нужно обновить файл .env нашего приложения Next.js, чтобы он содержал это значение.
// .env
// other secrets
AUTH0_HOOK_SECRET=that-super-secret-value-that-no-one-else-knows
И оберните нашу логику создания пользователя проверкой, чтобы убедиться, что значение правильное.
const { email, secret } = JSON.parse(req.body)
if (secret === process.env.AUTH0_HOOK_SECRET) {
// create user
} else {
console.log('You forgot to send me your secret!')
}
Весь маршрут API должен выглядеть примерно так.
// pages/api/auth/hooks.js
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
module.exports = async (req, res) => {
try {
const { email, secret } = JSON.parse(req.body)
if (secret === process.env.AUTH0_HOOK_SECRET) {
const user = await prisma.user.create({
data: { email },
})
console.log('created user')
} else {
console.log('You forgot to send me your secret!')
}
} catch (err) {
console.log(err)
} finally {
await prisma.$disconnect()
res.send({ received: true })
}
}
Следуйте той же логике из Хостинг на Vercel, автоматическое развертывание с помощью GitHub и настройка пользовательских доменов, чтобы добавить наши новые секреты Auth0 в Vercel — без этого наше размещенное приложение не будет работать.
Отлично! Вот и все! Мы сделали это!
Теперь каждый раз, когда новый пользователь входит в наше приложение Next.js, Auth0 сообщит нам об этом, чтобы мы могли создать пользователя в нашей базе данных Prisma, чтобы отслеживать те дополнительные биты данных, о которых заботится наше приложение!
Подписывайтесь на меня
"Веб-сайт"
"YouTube"