Как стать автором
Обновить

Авторизация и управление доступом на основе ролей для фронтенда

Уровень сложностиПростой
Время на прочтение4 мин
Количество просмотров6K

В этом модуле проекта мы погрузимся в волнующий мир авторизации и управления доступом во фронтенд-разработке. Сегодня я поделюсь с вами моим опытом работы с технологиями Vue 3, Pinia для глобального управления состоянием и TypeScript. Однако, стоит отметить, что основные принципы, которые мы рассмотрим здесь, применимы к любым современным технологиям фронтенда. Таким образом, даже если вы предпочитаете другой стек технологий, вы все равно найдете этот материал полезным.

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

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

Начало

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

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

state: () => {
        const currentUserString = localStorage.getItem('current_user');
        const currentUser = currentUserString ? JSON.parse(currentUserString) : {};
        return {
            current_user: currentUser as CurrentUser,
        }
},
      
actions: {
  async logIn(payload: UserAuth): Promise<AxiosResponse> {
    try {
        const res = await axios.post('/api-auth/Auth/Login', {...payload});
        if (res.data) {
            token.save(res.data);
            await getMe();
            return res;
        }
    } catch (error) {
        throw new Error('Ошибка входа. Пожалуйста, попробуйте еще раз.');
    }
  }

  async getMe(): Promise<AxiosResponse> {
    try {
        const res = await axios.get('/api-admin/User/GetMe');
        if (res && res.data) {
            const usersStore = useUsersStore(); // Предполагается, что useUsersStore() возвращает хранилище пользователей
            usersStore.current_user = res.data;
            localStorage.setItem('current_user', JSON.stringify(res.data));
            return res;
        }
    } catch (error) {
        throw new Error('Ошибка получения данных пользователя.');
    }
  }
}
interface Token {
  accessToken?: string;
  refreshToken?: string;
}

export const token = {
    save(value: Token) {
        if (value.accessToken) {
            Cookies.set(StorageItems.ACCESS_TOKEN, value.accessToken);
        }
        if (value.refreshToken) {
            Cookies.set(StorageItems.REFRESH_TOKEN, value.refreshToken);
        }
    },
    get():Token {
        return {
            accessToken: Cookies.get(StorageItems.ACCESS_TOKEN),
            refreshToken: Cookies.get(StorageItems.REFRESH_TOKEN)
        }
    },
    remove() {
        Cookies.remove(StorageItems.ACCESS_TOKEN);
        Cookies.remove(StorageItems.REFRESH_TOKEN);
    },
}

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

Для удобства работы с токенами мы создаем сервис, который позволяет сохранять, получать и обновлять токены. В данном случае, мы сохраняем полученный токен в cookie пользователя.

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

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

Ограничения

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

Однако, для удобства использования и понимания кода, мы можем создать файл enum.ts и определить в нем перечисление (enum), где каждый доступ будет иметь понятное значение, ассоциированное с определенным числовым значением.

export const enum PERMISSIONS {
    USER_ADD = 101,
    USER_EDIT = 102,
    USER_VIEW = 103,
    USER_DELETE = 104,
    ORGANIZATION_ADD = 105,
    ORGANIZATION_EDIT = 106,
    ORGANIZATION_VIEW = 107,
    ORGANIZATION_DELETE = 108,
}

Такой подход позволяет избежать использования "магических" чисел в коде и делает его более понятным и поддерживаемым.

Обработка доступов

Чтобы обрабатывать доступы пользователя, мы можем создать еще один сервис, который будет называться userCan. Этот сервис будет принимать в качестве аргумента одно или несколько числовых значений доступов и возвращать булево значение, указывающее, имеет ли текущий пользователь указанные доступы или нет.

export function userCan(permissions: number | number[]): boolean {
  const store= useUsersStore();
  return store.current_user.permissions.includes(permissions);
}

Далее

После создания функции userCan, нам остается просто использовать ее везде, где нам нужно будет выполнять проверки доступов пользователя. Вот несколько примеров:

const initialFetch = async () => {  
  if (userCan(PERMISSIONS.ORGANIZATION_VIEW)) {
    await organizationStore.fetchOrganizations();
  }
}

В этом примере мы используем функцию userCan для проверки, имеет ли текущий пользователь доступ к просмотру организаций. Если доступ есть, мы вызываем метод fetchOrganizations из хранилища организаций.

<div class="d-flex" v-if="userCan(PERMISSIONS.ORGANIZATION_EDIT)">
   <el-switch @change="statusHandler(scope.row)" v-model="scope.row.isActive"/>
</div>

Здесь мы использовали userCan в шаблоне Vue.js для условного отображения элемента. Если пользователь имеет доступ к редактированию организаций, отображается переключатель состояния (el-switch), позволяющий изменять статус элемента.

Теги:
Хабы:
Всего голосов 5: ↑5 и ↓0+5
Комментарии3

Публикации

Истории

Работа

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
11 сентября
Митап по BigData от Честного ЗНАКа
Санкт-ПетербургОнлайн
14 сентября
Конференция Practical ML Conf
МоскваОнлайн
19 сентября
CDI Conf 2024
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн
25 сентября
Конференция Yandex Scale 2024
МоскваОнлайн
28 – 29 сентября
Конференция E-CODE
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн