Универсальное руководство о том, как создать часть аутентификации в вашем приложении React.
Платформа авторизации OAuth 2.0
Прежде чем мы начнем, необходимо представить определение и использование токенов аутентификации, а именно токенов доступа и токенов обновления.
Жетоны доступа
Токены доступа используются при аутентификации на основе токенов, чтобы разрешить доступ к API. Токены доступа получаются после того, как пользователи успешно аутентифицируются и авторизуются. Затем эти токены передаются в качестве учетных данных при вызове API. Маркер доступа сообщает API, что его носитель авторизован для доступа к API и выполнения определенных действий, если они попадают в область действия, предоставленную во время авторизации.
Обновить токены
В основном, токены обновления используются для получения новых токенов доступа. Время жизни токена обновления обычно намного больше, чем у токена доступа. Он используется, когда срок действия токена доступа пользователя истекает или становится недействительным, чтобы пользователь мог восстановить доступ, получив новый токен доступа.
В этом уроке я буду использовать Axios
Axios - это библиотека JavaScript, используемая для выполнения HTTP-запросов из Node.js или XMLHttpRequests из браузера, который также поддерживает ES6 Promise API.
Вот полная статья, объясняющая плюсы использования Axios.
Перехватчики Axios:
Перехватчики используются для изменения заголовков запросов определенным образом для реализации какой-то глобальной схемы обработки ошибок.
Обработка ошибки может быть выполнена путем перехвата любого запроса, если он не удался, и выполнения некоторого кода, если он соответствует некоторым условиям error.
axiosInstance.interceptors.response.use( response => { return response; }, error => { if (errorSatifiesConditions(error)) { return doSomething().then({.... // could be to resend original request )} } else { doSomethingElse()...
Знакомство с хуками React - React v16, февраль 2019 -
В этом руководстве я буду использовать хуки React, а именно: useState и useContext.
Давайте начнем
Я расскажу вам о том, как модуль аутентификации должен запускаться и обслуживаться, а затем разобью его на более мелкие компоненты, чтобы вы все поняли.
Объясняя логику
Самый первый шаг - это когда пользователь вводит URL. ваше приложение должно проверить localStorage, если этот пользователь входил в систему раньше.
- A- Если есть информация о пользователе в localStorage, ваше приложение React продолжит проверять, активен ли access_token пользователя.
Еслиaccess_token
активен, пользователь в порядке, и они успешно прошли аутентификацию.
Еслиaccess_token
истек, ваше приложение должно отправитьrefreshToken
запрос и получить новыйaccess_token
- B. Если в localStorage нет информации о пользователе, ваше приложение должно перенаправить пользователя на страницу входа, где они смогут указать имя пользователя и пароль (или страницу регистрации).
За A отвечает функция getUser()
, за B отвечает функция login(username, password)
.
Итак, основная идея заключается в том, что после запуска приложение вызывает getUser()
, который либо аутентифицирует пользователя, либо нет.
Если нет, приложение вызывает login
с учетные данные пользователя, и функция login
вызывает getUser
для получения информации currUser
.
Чтобы было проще
constants.js
Создайте файл constants.js, который будет содержать все константы, чтобы вам не приходилось вводить его каждый раз при вызове конечной точки.
С axios.create
это еще более абстрактно и проще в использовании.
my_app - это экземпляр axios.
import axios from "axios"; export const URL = "https://your/url.com"; export const API_URL = "https://your/api/url.com"; export const my_app = axios.create({baseURL: API_URL});
Настройка вызова API - auth.js
Создайте файл auth.js, который будет содержать все ваши функции.
1. refreshToken
Базовый вызов конечной точки обновления.
const refreshToken = () => { let currUser = JSON.parse(localStorage.getItem("my_app_user")); let getUserFormData = new FormData(); getUserFormData.append("grant_type", "refresh_token"); getUserFormData.append("refresh_token", currUser.refresh_token); return new Promise((resolve, reject) => { my_app .post(`${URL}/token/url/`, getUserFormData, { headers: { Authorization: "Basic {secret_key}" } }) .then(async response => { resolve(response); }) .catch(error => { reject(error); }); }); };
2. getUser
getUser
отвечает за проверку наличия пользователя, хранящегося в localStorage. Если не находит, возвращается ноль.
Если он находит пользователя в localStorage ,, он получает дату последнего access_token
обновления этого пользователя, вычитает ее из текущего времени и сравнивает ее со сроком действия access_token
. Если он все еще действителен, он продолжается и получает currUser
информацию.
Если он недействителен, он вызывает refreshToken
endpoint и заменяет точку доступа новой и сбрасывает lastRefresh
в currUser
в localStorage.
Не забудьте заменить заголовок вашего Экземпляр axios my_app
с новым токеном доступа.
my_app.defaults.headers.common["Authorization"] = "Bearer " + currUser.access_token;
Перехватчики Axios:
Вы будете использовать перехватчики Axios, чтобы перехватить любой запрос, если он завершился неудачно с кодом состояния ошибки, представляющим истекший токен доступа (обычно: 401, сообщения об ошибке различаются). Это означает, что авторизация не удалась, потому что access_token
истек.
В этом случае вам нужно отправить запрос refreshToken
, а затем повторно отправить любой запрос, который изначально не удался, с новым действительным access_token
, возвращенным из refreshToken
запроса.
my_app.interceptors.response.use( response => { return response; }, error => { if (error.response.status === 401 && error.response.message === "blablabla") { return refreshToken().then({.... // resend original request
Наконец, если запросы login
или getUserInfo
или refreshToken
завершились неудачно, вы должны выйти из системы и удалить currUser
from localStorage, так как это безопаснее и перенаправит пользователя на страницу входа. Здесь вы также можете обрабатывать определенные типы ошибок. например, конкретная сетевая ошибка и т. д.
Одна из причин, по которой запрос refreshToken
может завершиться ошибкой, - это редкий случай, когда refresh_token
истек. Жетоны запросов имеют относительно гораздо более длительный срок службы, чем access_token
, но его истечение все еще возможно. В этом случае вам следует перенаправить пользователя на страницу входа, используя logout
, как упоминалось ранее.
const getUser = () => { let currUser = JSON.parse(localStorage.getItem("my_app_user")); if (!currUser) { // if no user in localStorage then the user must enter their credentials to proceed return Promise.resolve(null); } // get the expiry time of the current access token and measure whether it expired or not let currDate = new Date(); let diff = currDate.getTime() - currUser.lastRefresh; if (diff >= currUser["expires_in"] * 1000) { // access token expired need to refresh token // you could just call refreshToken I just typed it in so you could see it clearer // call refreshToken() // if success, update currUser with the new access_token and save this instant to be the last_refresh (for future refresh) and update axios headers // if fail logout() } else { // Do not need refresh my_app.defaults.headers.common["Authorization"] = "Bearer " + currUser.access_token; my_app.interceptors.response.use( response => { return response; }, error => { if (error.response.status === 401 && some other condition that sprecifies an expir state){ // 401 is Unauthorized error // which means that this request failed // what we need to do is send a refresh request then resend the same request // that failed but with the new access point. // We can do this automatically using axios interceptors return refreshToken() .then(response => { // update currUser with new access_token // Set default headers to have authorization the access token as authorization for future requests // Get the original that failed due to 401 and resend it // with the new access token const config = error.config; config.headers.Authorization = "Bearer " + response.data.access_token; // Resending original request return new Promise((resolve, reject) => { my_app .request(config) .then(response => { resolve(response); }) .catch(error => { reject(error); }); }); }) .catch(error => { // just logout() if anything goes wrong}); } logout(); return new Promise((resolve, reject) => { reject(error); }); } ); // finally get user return my_app.get("/users/current/url").catch(error => { logout(); throw error; }); } };
3. login
Эта функция просто получает имя пользователя и пароль и отправляет их для аутентификации вместе с секретным ключом.
const login = (username, password) => { let loginFormData = new FormData(); loginFormData.append("grant_type", "password"); loginFormData.append("username", username); loginFormData.append("password", password); return new Promise((resolve, reject) => { axios .post(`${URL}/token/url`, loginFormData, { headers: { Authorization: "Basic {secret_key}" } }) .then(async response => { response.data.lastRefresh = new Date().getTime(); localStorage.setItem("my_app_user", JSON.stringify(response.data)); getUser() .then(response => { resolve(response); }) .catch(error => { reject(error); }); }) .catch(error => { reject(error); }); }); };
4. logout
Обычно просто удаляет currUser
из localStorage.
const logout = () => { localStorage.removeItem("my_app_user"); return Promise.resolve(); };
5. Контекст аутентификации
Этот контекст будет постоянно содержать вашу пользовательскую переменную. Из user
вы сможете узнать.
- Независимо от того, вошел ли пользователь в систему или нет.
- Информация о пользователе.
import React, { createContext, useState } from "react"; import auth from "../helpers/auth"; import LoadingPage from "../components/LoadingPage"; export const AuthenticationContext = createContext(); const AuthenticationContextProvider = props => { const [user, setUser] = useState(null); const [fetched, setFetched] = useState(false); const login = (username, password) => { return auth.login(username, password).then(data => { setUser(data.data); }); }; const logout = async () => { setUser(null); await auth.logout(); }; const getUser = async () => { const user = await auth.getUser(); setUser(user ? user.data : null); }; if (!fetched) { getUser().then(() => setFetched(true)); } if (!fetched) { return <LoadingPage />; } return ( <AuthenticationContext.Provider value={{ user, login, logout, getUser, setUser }} > {props.children} </AuthenticationContext.Provider> ); }; export { AuthenticationContextProvider };
fetched
сообщает, что вы пытались получить currUser
, и если он был обнаружен, информация о пользователе будет в user
, иначе она будет пустой.
Ниже приведена часть кода, запускающая процесс аутентификации, о котором говорилось ранее.
if (!fetched) { getUser().then(() => setFetched(true)); }
6 . последний шаг
Этот шаг является вашим переключателем аутентификации, который перенаправляет пользователя на аутентифицированные страницы или неаутентифицированную страницу входа в систему на основе user
в контексте.
const { user } = useContext(AuthenticationContext); return user ? <AuthenticatedApp /> : <UnauthenticatedApp />;
Важная заметка
Размещение ваших маршрутов и маршрутизатора в Authenticatedapp
значительно упростит вам маршрутизацию; пользователь будет перенаправлен на соответствующий маршрут к URL-адресу, который они ввели, если они были успешно аутентифицированы, а не вы выполняете маршрутизацию вручную.
И наконец. Это было полное руководство по аутентификации на основе токенов с помощью React. Надеюсь, вы узнали. Если у вас есть вопросы, не стесняйтесь спрашивать.
Вы найдете все файлы кода в этом репозитории GitHub.
Примечание от Plain English
Вы знали, что у нас четыре публикации? Проявите немного любви, предложив им следующие слова: JavaScript на простом английском, AI на простом английском, UX на простом английском , Python на простом английском - спасибо и продолжайте учиться!
Кроме того, мы всегда заинтересованы в продвижении хорошего контента. Если у вас есть статья, которую вы хотели бы отправить в какую-либо из наших публикаций, отправьте электронное письмо по адресу [email protected] со своим именем пользователя Medium и тем, о чем вы хотите написать, и мы вернуться к вам!