Аутентификация и авторизация - общие проблемы при написании приложения. Аутентификация отвечает на вопрос «кто вы?», А авторизация отвечает на вопрос «вам разрешено это видеть?»

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

Это сработает, но я вижу в этом несколько проблем.

  • Нарушения принципа единоличной ответственности.
  • Излишняя передача данных через React Router.
  • Нет повторно используемого кода.

Итак, как мы можем применить авторизацию таким образом, чтобы каждый компонент выполнял отдельную работу, был самодостаточным и допускал повторное использование?

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

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

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

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

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

Обновление: было указано, что обертывание компонента в HOC внутри рендера может вызвать множество проблем, и, как правило, этого делать не следует. В любом случае замена вызова встроенной функции HOC в component={} должна быть заменена назначением его переменной вне метода рендеринга.

const RouteWithAuthorization = Authorization(YourRoute, ['roles'])

Дополнительную информацию о том, почему это плохая идея, можно найти в документации по React. Вернуться к исходному сообщению.

Большой! Теперь в нашем обработчике маршрута есть намек на логику авторизации, через React Router ничего не проходит, и все наши разрешенные роли определены в одном файле. Но вы знаете, что, если у нас много маршрутов, это будет означать, что определения ролей будут дублироваться.

Сделаем два изменения: изменим порядок аргументов, которые мы передаем HOC, и карри функцию. Наш HOC становится функцией, возвращающей HOC. Этот тип вызова функции должен показаться вам знакомым, если вы используете Redux, поскольку это тот же механизм, который используется connect.

Теперь мы можем «засеять» нашу авторизационную HOC разными уровнями разрешенных ролей, устраняя необходимость многократно определять разрешенные роли.

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

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

Изменить: повторить вышесказанное, создание компонентов более высокого порядка в рендере вызовет проблемы с тем, как React вычисляет различия. В последнем примере выше, части User(User), Manager(EditUser) и Admin(CreateUser) должны быть извлечены из метода рендеринга и назначены переменным.

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

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

Возможно, в вашем приложении HOC авторизации передает текущую роль как опору в компонент, который он обертывает. Возможно, один из каррированных аргументов - это компонент, который нужно отобразить в случае сбоя авторизации. Возможно, вместо компонента более высокого порядка это компонент с дочерней функцией, которая получает роль и информацию о том, прошла ли авторизация в качестве аргументов. Может быть, вместо массива разрешенных ролей вы передадите функцию с подписью user => bool.

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

Спасибо за прочтение! Я в Твиттере как @vcarl_ (но в большинстве других мест я vcarl). Я модерирую Reactiflux, чат для разработчиков React, и Nodeiflux, чат для разработчиков Node.JS. Если у вас есть какие-либо вопросы или предложения, обращайтесь!