Рубрики
Без рубрики

Реагистрировать токен auth.

Разрешение проблемы является одной из первых проблем разработчиков, с которыми сталкивается, начиная новый проект. И один из наиболее распространенных видов авторизации (от моего опыта) – это токен …

Автор оригинала: Oleg Babichev.

Проблема

Авторизация является одной из первых проблем разработчиков, которым сталкиваются разработчики на начало нового проекта. И одним из наиболее распространенных видов авторизации (из моего опыта) является авторизация на основе токена (обычно используя JWT).

С моей точки зрения, эта статья выглядит как «что я хотел прочитать две недели назад». Моя цель состояла в том, чтобы написать минималистичный и многоразовый код с чистым и простым интерфейсом. У меня были следующие требования для моей реализации управления AUTH:

  • Токены должны храниться в местном хранении
  • Токены должны быть восстановлены на странице перезагрузить
  • Токен доступа должен быть передан в сетевые запросы
  • После того, как токен доступа доступа должен быть обновлен, если представлен последний
  • Компоненты реагирования должны иметь доступ к информации о авторизации для подходящих пользовательских интерфейсов
  • Решение должно быть сделано с чистым реагированием (без redux, Thunk и т. Д.)

Для меня один из самых сложных вопросов был:

  • Как сохранить синхронные компоненты React Components и локальных данных хранения?
  • Как получить токен внутри извлечения, не передавая его через все элементы дерева (особенно если мы хотим использовать эту привлечение в действиях Teunk позже)

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

Я просто хочу устанавливать NPM … и пойти на производство

Я уже собрал пакет, который содержит все описанные ниже (и немного больше). Вам просто нужно установить его по команде:

npm install react-token-auth

И следуйте примерам в React-Token-Auth Репозиторий Github.

Решение

Перед решением проблемы я сделаю предположение, что у нас есть бэкэнда, которая возвращает объект с доступом и токенами обновления. У каждого токена есть Jwt формат. Такой объект может выглядеть так:

{
  "accessToken": "...",
  "refreshToken": "..."
}

На самом деле структура объекта токенов не имеет решающее значение для нас. В простейшем случае это может быть строка с бесконечным токеном доступа. Но мы хотим посмотреть, как управлять ситуацией, когда у нас есть два токена, один из них может истекать, а второй может быть использован для обновления первого.

Jwt.

Если вы не знаете, что такое токен JWT Лучший вариант, чтобы пойти в jwt.io И посмотрите, как это работает. Теперь важно, чтобы токен JWT содержит кодировку (в формате base64 Формат) информацию о пользователе, который позволяет аутентифицировать его на сервере.

Обычно JWT токен содержит 3 части, разделенные точками и выглядит как:

eyjhbgcioijiuzi1niisinr5ciijiuzi1niisinr5cci6ikpxvcj9.eyjuyw1lijoism9Obibeb2uilcjpyxquize1mtymzkwmjisimv4cci6mtuxnjizotaymn0.yozc0rjfsopcpj-d3bwe8-bkolr_scqqpdjpq8wn-1mc

Если мы декодируем среднюю часть ( Eyju ... mn0 ) этого токена мы получим следующий JSON:

{
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516239022
}

С этой информацией мы сможем получить срок годности токена.

Токен провайдер

Как я уже упоминал, наш первый шаг создает провайдер токена. Поставщик токена будет работать непосредственно с местным хранением, и все изменения токена мы сделаем через него. Это позволит нам слушать изменения из любого места и немедленно уведомить слушателей об изменениях (но об этом немного позже). Интерфейс провайдера будет иметь следующие методы:

  • Геттокен () Чтобы получить текущий токен (он будет использоваться в Fetch)
  • Метанк () Чтобы установить токен после входа в систему, выйти из системы или регистрации
  • isloggedin () Чтобы проверить, пользователь вошел в систему
  • Подписаться () Чтобы дать провайдеру функцию, которая должна называться после любого изменения токена
  • Отписаться () удалить абонент

Функция CreateTokenProvider () Создает экземпляр провайдера токена с описанным интерфейсом:

const createTokenProvider = () => {

    /* Implementation */

    return {
        getToken,
        isLoggedIn,
        setToken,
        subscribe,
        unsubscribe,
    };
};

Все следующий код должен быть внутри функции CreateTokenProvider.

Давайте начнем с создания переменной для хранения токенов и восстановление данных из локального хранилища (чтобы убедиться, что сеанс не будет потерян после перезагрузки страницы):

let _token: { accessToken: string, refreshToken: string } = 
    JSON.parse(localStorage.getItem('REACT_TOKEN_AUTH') || '') || null;

Теперь нам нужно создать некоторые дополнительные функции для работы с токенами JWT. В текущий момент токен JWT выглядит как волшебная строка, но не имеет большого значения, чтобы разбирать его и попытаться извлечь дату истечения срока действия. Функция getexpatitiondate () Займет токен JWT в качестве параметра и возврата срок годности времени срок действия времени на успех (или NULL ON FOUNE):

const getExpirationDate = (jwtToken?: string): number | null => {
    if (!jwtToken) {
        return null;
    }

    const jwt = JSON.parse(atob(jwtToken.split('.')[1]));

    // multiply by 1000 to convert seconds into milliseconds
    return jwt && jwt.exp && jwt.exp * 1000 || null;
};

И еще одна функция UTIL Isexpired () Чтобы проверить, истек временной компьютер. Эта функция возвращает true, если представлена температура истечения срока действия и, если он меньше, чем Date.now () Отказ

const isExpired = (exp?: number) => {
    if (!exp) {
        return false;
    }

    return Date.now() > exp;
};

Время для создания первой функции интерфейса провайдера токена. Функция Геттокен () Должен вернуть токен и обновлять его, если это необходимо. Эта функция должна быть async Потому что это может сделать сетевой запрос на обновление токена.

Используя созданные более ранние функции, которые мы можем проверить, это токены доступа истек или нет ( Isexpired (GetExisateDAted (_Token.accessToken)) ). И в первом случае, чтобы сделать запрос на обновление токена. После этого мы можем сохранить токены (с не реализованными еще функцией Setting () ). И, наконец, мы можем вернуть токен доступа:

const getToken = async () => {
    if (!_token) {
        return null;
    }

    if (isExpired(getExpirationDate(_token.accessToken))) {
        const updatedToken = await fetch('/update-token', {
            method: 'POST',
            body: _token.refreshToken
        })
            .then(r => r.json());

        setToken(updatedToken);
    }

    return _token && _token.accessToken;
};

Функция isloggedin () будет просто: это вернется, если _tokens не null И не будет проверяться на токен доступа (в этом случае мы не будем знать о токене доступа к истечении истечения срока действия до тех пор, пока мы не получим неудачу при получении токена, но обычно достаточно, и будем поддерживать функцию Isloggedin Synchronous):

const isLoggedIn = () => {
    return !!_token;
};

Я думаю, что это хорошее время для создания функциональности для управления наблюдателями. Мы осуществим что-то похожее на Узор наблюдателя И прежде всего, создаст массив для хранения всех наших наблюдателей. Мы ожидаем, что каждый элемент в этом массиве является функцией, которую мы должны позвонить после каждого изменения токенов:

let observers: Array<(isLogged: boolean) => void> = [];

Теперь мы можем создавать методы Подписаться () и Отписаться () Отказ Первый добавит новый наблюдатель в созданный более ранний массив, второй удалит наблюдатель из списка.

const subscribe = (observer: (isLogged: boolean) => void) => {
    observers.push(observer);
};

const unsubscribe = (observer: (isLogged: boolean) => void) => {
    observers = observers.filter(_observer => _observer !== observer);
};

Вы уже можете увидеть из интерфейса функций Подписаться () и Отписаться () То, что мы отправим к наблюдателям только факт – это факт – это вошел в систему. Но в целом вы можете отправить все, что вы хотите (весь токен, время и т. Д.). Но для наших целей будет достаточно, чтобы отправить логический флаг.

Давайте создадим маленькую функцию UTIL Уведомить () Это займет этот флаг и отправить всем наблюдателям:

const notify = () => {
    const isLogged = isLoggedIn();
    observers.forEach(observer => observer(isLogged));
};

И последнее, но не менее важное значение, нам нужно реализовать, это Метанк () Отказ Целью этой функции является экономия токенов в локальном хранилище (или чистое локальное хранилище, если токен пуст) и уведомляющий наблюдателей об изменениях. Итак, я вижу цель, я иду к цели.

const setToken = (token: typeof _token) => {
    if (token) {
        localStorage.setItem('REACT_TOKEN_AUTH', JSON.stringify(token));
    } else {
        localStorage.removeItem('REACT_TOKEN_AUTH');
    }
    _token = token;
    notify();
};

Будьте уверены, если вы пришли к этому вопросу в статье и нашли его полезными, вы уже сделали меня счастливее. Здесь мы закончим с провайдером токена. Вы можете посмотреть свой код, играть с ним и убедитесь, что он работает. В следующей части на вершине этого мы создадим больше абстрактных функций, которые будут уже полезны в любом реактивном приложении.

Поставщик авторизации

Давайте создадим новый класс объектов, которые мы будем звонить как поставщик авторизации. Интерфейс будет содержать 4 метода: крючок Useauth () Чтобы получить новый статус из компонента React, authfetch () Чтобы сделать запросы в сеть с фактическим токеном и Войти () , Выход () Методы, которые будут прокси призывы к способу Метанк () Token Provider (в этом случае у нас будет только одна точка входа в целую созданную функциональность, а остальная часть кода не придется знать о существующих поставщике токена). Как до того, как мы начнем с функционального создателя:

export const createAuthProvider = () => {

    /* Implementation */

    return {
        useAuth,
        authFetch,
        login,
        logout
    }
};

Прежде всего, если мы хотим использовать провайдеру токена, нам нужно создать его экземпляр:

const tokenProvider = createTokenProvider();

Методы Войти () и Выход () Просто пройдите токен к провайдеру токена. Я разлучил эти методы только для явного значения (фактически прохождение пустой/нулевой токен удаляет данные из локального хранения):

const login: typeof tokenProvider.setToken = (newTokens) => {
    tokenProvider.setToken(newTokens);
};

const logout = () => {
    tokenProvider.setToken(null);
};

Следующий шаг – функция Fetch. Согласно моей идее, эта функция должна иметь именно тот же интерфейс, что и оригинальная выборка, и вернуть тот же формат, но должен ввести токен доступа к каждому запросу.

Функция Fetch должна принимать два аргумента: запросить информацию (обычно URL) и запрос init (объект с методом, телом. Заголовки и т. Д.); и возвращает обещание для ответа:

const authFetch = async (input: RequestInfo, init?: RequestInit): Promise => {
    const token = await tokenProvider.getToken();

    init = init || {};

    init.headers = {
        ...init.headers,
        Authorization: `Bearer ${token}`,
    };

    return fetch(input, init);
};

Внутри функции мы сделали две вещи: взяли токен из провайдера токена по заявлению ждать tokenprovider.gettoken (); ( GetToken уже содержит логику обновления токена после истечения срока действия) и впрыскивая этот токен в Авторизация Заголовок по линии Авторизация: «Носитель $ {токен}» Отказ После этого мы просто вернемся с обновленными аргументами.

Итак, мы уже можем использовать провайдер авторизы, чтобы сохранить токены и использовать их из Fetch. Последняя проблема в том, что мы не можем реагировать на изменение токена с наших компонентов. Время решить это.

Как я сказал, прежде чем мы создадим крючок Useauth () Это предоставит информацию компоненту – это вошел в систему или нет. Чтобы быть в состоянии сделать это, мы будем использовать крючок Уместите () Чтобы сохранить эту информацию. Это полезно, потому что любые изменения в этом состоянии приведут к реранду компонентов, которые используют этот крюк.

И мы уже подготовили все возможное, чтобы слушать изменения в местном хранилище. Общий способ слушать любые изменения в системе с крючками, используют крюк Useffect () Отказ Этот крюк принимает два аргумента: функция и список зависимостей. Функция будет уволена после первого звонка Useffect А затем отменил после любых изменений в списке зависимостей. В этой функции мы можем начать слушать изменения в локальном хранилище. Но что важно, мы можем вернуться из этой функции … Новая функция и, эта новая функция будет уволена либо перед отправкой первого или после размонтирования компонента. В новой функции мы можем перестать слушать изменения и гарантии реагирования, что эта функция будет выпущена (по крайней мере, если не происходит исключение в ходе этого процесса). Звучит немного сложно, но просто посмотрите на код:

const useAuth = () => {
    const [isLogged, setIsLogged] = useState(tokenProvider.isLoggedIn());

    useEffect(() => {
        const listener = (newIsLogged: boolean) => {
            setIsLogged(newIsLogged);
        };

        tokenProvider.subscribe(listener);
        return () => {
            tokenProvider.unsubscribe(listener);
        };
    }, []);

    return [isLogged] as [typeof isLogged];
};

И это все. Мы только что создали компактный и многоразовый хранилище токенов с помощью Clear API. В следующей части мы рассмотрим некоторые примеры использования.

Применение

Чтобы начать использовать то, что мы реализовали выше, нам нужно создать экземпляр поставщика авторизации. Это даст нам доступ к функциям Useauth () , authfetch () , Войти () , Выход () Связанные с тем же токеном в местном хранилище (в целом, ничто не мешает вам создавать различные экземпляры поставщика авторизации для разных токенов, но вам нужно будет параметризировать ключ, который вы используете для хранения данных в локальном хранилище):

export const {useAuth, authFetch, login, logout} = createAuthProvider();

Форма входа

Теперь мы можем начать использовать функции, которые мы получили. Начнем с компонента входа в систему. Этот компонент должен предоставлять входы для учетных данных пользователя и сохранить его во внутреннем состоянии. При отправке нам нужно отправить запрос с учетными данными, чтобы получить токены, а здесь мы можем использовать функцию Войти () Хранить полученные токены:

const LoginComponent = () => {
    const [credentials, setCredentials] = useState({
        name: '',
        password: ''
    });

    const onChange = ({target: {name, value}}: ChangeEvent) => {
        setCredentials({...credentials, [name]: value})
    };

    const onSubmit = (event?: React.FormEvent) => {
        if (event) {
            event.preventDefault();
        }

        fetch('/login', {
            method: 'POST',
            body: JSON.stringify(credentials)
        })
            .then(r => r.json())
            .then(token => login(token))
    };

    return 
};

И вот и все, это все, что нам нужно для хранения токена. После этого, когда токен получен, нам не нужно будет применять дополнительные усилия, чтобы привести его к привлечению или в компонентах, потому что он уже реализован внутри поставщика авторизации.

Аналогичная форма аналогична, есть только различия в количестве и именах входных полей, поэтому я буду пропустить его здесь.

Маршрутизатор

Кроме того, мы можем реализовать маршрутизацию с помощью поставщика авторизации. Предположим, что у нас есть два пакета маршрутов: один для зарегистрированного пользователя и один для не зарегистрирован. Чтобы разделить их, нам нужно проверить, у нас есть токен в локальном хранилище или нет, а здесь мы можем использовать крючок Useauth () :

export const Router = () => {
    const [logged] = useAuth();

    return 
        
            {!logged && <>
                
                
                
            }
            {logged && <>
                
                
            }
        
    ;
};

И хорошая вещь, которую он будет перераспределен после каких-либо изменений в местном хранилище, из-за Useauth Имеет подписку на эти изменения.

Запрашивать запросы

И тогда мы можем получить данные, защищенные токеном, использующим authfetch Отказ Он имеет тот же интерфейс, что и Fetch, поэтому, если вы уже используете Fetch в коде, вы можете просто заменить его по authfetch :

const Dashboard = () => {
    const [posts, setPosts] = useState([]);

    useEffect(() => {
        authFetch('/posts')
            .then(r => r.json())
            .then(_posts => setPosts(_posts))
    }, []);

    return 
{posts.map(post =>
{post.message}
)}
};

Резюме

Мы сделали это. Это было интересное путешествие, но у него также есть конец (может быть, даже счастлив).

Мы начали с понимания проблем с хранением авторизации токенов. Затем мы реализовали решение и, наконец, посмотрели на примеры того, как он может быть использован в приложении React.

Как я уже говорил, вы можете найти мою реализацию на GitHub в библиотеке. Он решает немного более общую проблему и не делает предположения о структуре объекта с токенами или как обновить токен, поэтому вам нужно будет предоставить некоторые дополнительные аргументы. Но идея решения одинаково, и репозиторий также содержит инструкции о том, как его использовать.

Здесь я могу сказать спасибо за чтение статьи и надеюсь, что это было полезно для вас.