Как создать встроенный логин с помощью React

Auth0 — это поставщик «Аутентификация как услуга», что означает, что он обеспечивает реализацию аутентификации в вашем приложении без необходимости самостоятельной реализации полного потока. Следовательно, обработка токенов Id, Access и Refresh осуществляется самим Auth0, что позволяет вам сосредоточиться на приложении, которое вы создаете, и меньше беспокоиться о хранении и доступе к токенам и безопасности.
В этом блоге я расскажу, как я реализовал поток аутентификации с использованием Auth0 и React.
Возможно, вы сталкивались с пакетом «auth0-react» — пакетом, который является абстракцией ванильного пакета «auth0-js», который предоставляет API более высокого порядка, который значительно упрощает реализацию за счет использования Auth0. -предоставленная страница аутентификации — которая обрабатывает регистрацию и вход в систему (вы будете перенаправлены на эту страницу). Однако его можно настроить, если у вас есть учетная запись с активированным выставлением счетов.
Я буду использовать ванильный пакет «auth0-js», так как я буду использовать бесплатную учетную запись и хочу, чтобы процесс аутентификации происходил в моем приложении — встроенный вход в систему.
Установка
Для настройки панели управления Auth0 требуется несколько шагов.
- Перейдите на веб-сайт Auth0 и создайте нового «арендатора».

- Создайте новое приложение на боковой панели «Приложения» созданного арендатора.

- Перейдите на вкладку настроек созданного приложения.
- Добавьте URL-адреса, которые вы будете использовать при разработке, в следующих разделах. (Не забудьте обновить это всякий раз, когда вы используете другой локальный хост или после развертывания приложения).

- Включить ротацию токена обновления (если она не включена) — это понадобится нам для реализации постоянства пользователя при обновлении.
- Прокрутите вниз до «Дополнительных настроек» и нажмите на вкладку «Типы грантов». Убедитесь, что опция «Пароль» отмечена флажком.

- Нажмите на созданного арендатора в верхнем левом углу и перейдите в «Настройки».
- Нажмите на вкладку «Общие» и прокрутите, пока не найдете «Каталог по умолчанию» в разделе «Настройки авторизации API».
- Добавьте «Имя пользователя-Пароль-Аутентификация» в каталог по умолчанию. Убедитесь, что нет никаких опечаток.

- Перейдите к «Правилам» на боковой панели и «Создайте» новое «Пустое» правило. Это правило прикрепит атрибут «роль», который мы укажем, к объекту, который мы получим при аутентификации. Мы будем использовать этот атрибут для реализации авторизации.
– Добавьте название своего веб-сайта в<your-website>.. Убедитесь, что вы нередактируетеnamespace, кроме этого. (Имя правила может быть любым, которое вы предпочитаете).
— Это правило будет выполняться по запросу на вход, непосредственно перед выдачей токена id, тем самым внедряя роль в токен id.

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

- URL-адрес перенаправления относится к URL-адресу, на котором запущено приложение; Соединение с БД — это база данных, которую мы создали; Тип ответа указывает, в какой форме мы хотим получить ответ при входе в систему; Режим ответа указывает, где будет отображаться ответ — в нашем случае он будет добавлен к нашему URL-адресу в виде фрагмента, однако он не будет использоваться, поскольку мы будем использовать метод встроенной аутентификации.
- Наконец, создайте новый файл, реализующий «WebAuth», который поступает из пакета «auth0-js» следующим образом. (Нам нужен offline_access для получения токенов обновления)
import auth0 from 'auth0-js';
export const webAuth = new auth0.WebAuth({
domain: `${process.env.REACT_APP_AUTH0_DOMAIN}`,
clientID: `${process.env.REACT_APP_AUTH0_CLIENT_ID}`,
responseType: `${process.env.REACT_APP_AUTH0_RESPONSE_TYPE}`,
redirectUri: `${process.env.REACT_APP_REDIRECT_URL}`,
responseMode: `${process.env.REACT_APP_AUTH0_RESPONSE_MODE}`,
scope: 'openid profile email offline_access'
});
регистр
Теперь, когда базовая установка готова, мы можем перейти к мясу и картошке. Приведенный ниже фрагмент кода является примером процесса регистрации.
const loginUser = async () => {
webAuth.client.login({
realm: `${process.env.REACT_APP_AUTH0_DB_CONNECTION}`,
username: email,
password: password,
}, async (err, result) => {
if (err) {
return err;
}
await authenticate(result);
});
}
const webAuthLogin = async () => {
webAuth.signup({
connection: `${process.env.REACT_APP_AUTH0_DB_CONNECTION}`,
email,
password,
user_metadata: {
role: UserType.CUSTOMER,
},
}, async (err, result) => {
if (err) {
return err;
}
await loginUser();
});
}
Для регистрации требуется электронная почта/имя пользователя и пароль. Наряду с этим вы можете отправлять дополнительные метаданные для обогащения профиля пользователя в пределах user_metadata. Если вы помните, этот атрибут — это то, что мы называли получением атрибута роли.
Если с базовой настройкой все в порядке, этот запрос должен быть выполнен успешно, и вы сможете просмотреть этого пользователя на вкладке «Пользователи» в разделе «Управление пользователями».
Полученный результат будет обогащенным объектом, содержащим токены идентификатора и доступа. Вызываемая функция входа регистрирует зарегистрированного пользователя в приложении. Я займусь этим дальше.
Авторизоваться
Процесс входа в систему на первый взгляд относительно прост, как видно из фрагмента выше. Однако реализация функции аутентификации, которая вызывается при успешном ответе, требует немного больше усилий.
Следующий фрагмент — это функция authenticate.
const authenticate = async (result) => {
auth0Service.handleAuthentication(result);
await auth0Service.setUserProfile(result.accessToken, result.idToken, dispatch);
}
В приведенном выше фрагменте вызывается внешняя служба, которая выполняет закулисные функции, необходимые для удержания пользователя на обновлении страницы. Если настойчивость не нужна, этот шаг не требуется — будет достаточно полученного результата.
handleAuthentication предназначен для хранения токенов в session storage (local storage тоже подойдет).
public handleAuthentication(result: any): void {
if (result.idToken || result.id_token) {
this.setSession(result);
} else {
History.push('/');
window.location.reload();
}
}
private setSession(result: any) {
const expiresAt = result.expiresIn ? JSON.stringify(result.expiresIn * 1000 + new Date().getTime())
: JSON.stringify(result.expires_in * 1000 + new Date().getTime());
this.setSessionStorage(result, expiresAt);
}
private setSessionStorage(result: any, expiresAt: any): void {
sessionStorage.setItem('refresh_token', result.refreshToken ? result.refreshToken : result.refresh_token);
sessionStorage.setItem('expires_at', expiresAt);
}
В приведенном выше фрагменте результат передается в setSession, который получает время истечения срока действия токена, чтобы гарантировать, что можно использовать только токен, срок действия которого не истек. setSessionStorage сохраняет полученный токен обновления и время истечения срока действия в хранилище сеансов. (проверки для result.idToken &result.id_token и result.refreshToken & result.refresh_token являются единственными, потому что есть вероятность, что Auth0 вернет их либо как camelCase, либо как snake_case)
Причина, по которой токен обновления хранится в хранилище сеансов, а не идентификатор или токены доступа, заключается в том, чтобы избежать атак CSRF (поскольку они содержат конфиденциальную информацию). Однако токен обновления их не содержит — он используется исключительно для получения других токенов доступа, поэтому сам по себе не имеет смысла.
setUserProfile — это сохранение аутентифицированного пользователя в памяти — в данном случае это redux.
public async setUserProfile(
accessToken: string,
idToken: string,
dispatch: any,
): Promise<any> {
webAuth.client.userInfo(accessToken, async (err: any, result: any) => {
if (err) {
console.error('Something went wrong: ', err.message);
return;
}
return this.authenticateUser(
accessToken,
idToken,
result,
dispatch,
);
});
}
private async authenticateUser(
accessToken: string,
idToken: string,
result: any,
dispatch: any,
) {
dispatch(
login({
email: result?.email,
userType: result?.['https://<your-website>/claims/role'],
idToken,
accessToken,
})
);
}
В приведенном выше фрагменте полученный токен доступа используется для получения информации о пользователе, которая использовалась для регистрации. Затем эта информация отправляется в redux. (В правиле мы указали возвращать атрибут role в нашем объекте результата. Если требуется дополнительная информация, это так же просто, как добавить ее в то же правило 😁).
Сохранение при обновлении
Теперь, когда мы интегрировали часть постоянства в логин, в этом разделе основное внимание будет уделено восстановлению вошедшего в систему пользователя при обновлении.
App.jsx
useEffect(() => {
const dispatchUserData = (authResult) => {
const { user } = authResult.data;
dispatch(
login({
email: user?.email,
accessToken: authResult.access_token,
idToken: authResult.id_token,
userType: user?.user_metadata?.role,
})
);
}
const setAuthenticatedUser = async () => {
let authResult;
if (isUserAuthenticated) {
authResult = await auth0Service.getInitialAuthenticatedUser();
}
if (authResult) dispatchUserData(authResult);
}
setAuthenticatedUser();
}, [auth0Service, dispatch, isUserAuthenticated]);
External File
public async getInitialAuthenticatedUser(): Promise<any> {
if (sessionStorage.getItem('refresh_token')) {
const isUserAuthenticated = this.isAuthenticated();
const refreshTokenResponse = await this.getUserWithRefreshToken();
if (isUserAuthenticated && refreshTokenResponse) {
this.handleAuthentication(refreshTokenResponse);
const user = await getUser(refreshTokenResponse.access_token);
return { ...user, ...refreshTokenResponse };
}
}
}
public isAuthenticated(): boolean {
const date = sessionStorage.getItem('expires_at');
const refreshToken = sessionStorage.getItem('refresh_token');
if (date && refreshToken) {
const expiresAt = JSON.parse(date);
if (!refreshToken || (new Date().getTime() > expiresAt)) {
this.removeSessionStorage();
return false;
};
return true;
}
return false;
}
private async getUserWithRefreshToken(): Promise<any> {
const response = await axios.post(`https://${process.env.REACT_APP_AUTH0_DOMAIN}/oauth/token`,
{
grant_type: 'refresh_token',
client_id: `${process.env.REACT_APP_AUTH0_CLIENT_ID}`,
refresh_token: sessionStorage.getItem('refresh_token'),
client_secret: `${process.env.REACT_APP_AUTH0_CLIENT_SECRET}`
},
{ headers: { 'Content-Type': 'application/json', }, },
);
return response.data;
}
private async getUser(accessToken: string): Promise<any> {
webAuth.client.userInfo(accessToken, async (err: any, result: any) => {
if (err) {
console.error('Something went wrong: ', err.message);
return;
}
return result;
});
}
public removeSessionStorage(): void {
sessionStorage.removeItem('refresh_token');
sessionStorage.removeItem('expires_at');
}
Приведенный выше фрагмент помещается в файл App, поскольку он запускается при загрузке страницы. Определенный useEffect вызывает вспомогательную функцию для получения текущего пользователя, вошедшего в систему, и сохраняет их в избыточности.
getInitialAuthenticatedUser вызывает функцию, которая проверяет, аутентифицирован ли пользователь. Эта функция isUserAuthenticated проверяет, что токен, хранящийся в хранилище сеансов, не просрочен (если это так, она удаляет его и возвращает false — пользователя нет).
ФункцияgetUserWithRefreshToken говорит сама за себя. Он вызывает API вашего созданного приложения Auth0, передавая токен обновления, доступный в хранилище сеансов, для получения ответа. Та же процедура выполняется, когда вновь полученный токен обновления сохраняется в хранилище сеансов, переопределяя существующий в настоящее время.
getUser вызывается с полученным токеном доступа, который, наконец, возвращает пользовательский объект.
Поздравляем! Теперь у вас есть работающий поток аутентификации, реализованный с использованием Auth0 😁