shortener-server

Copyright(c) Mansur Ziiatdinov 2018-2019
LicenseBSD-3
Maintainerchgk@pm.me
Stabilityexperimental
PortabilityPOSIX
Safe HaskellNone
LanguageHaskell2010

Auth

Contents

Description

В этом модуле реализован API для работы с авторизацией, который описывается в AuthApi.

До разработки приложения

Для работы с сервером авторизации разработчику любого клиентского приложения необходимо выполнить следующие организационные шаги:

  • обратиться к администраторам сервера авторизации и зарегистрировать своё приложение на сервере авторизации;
  • при регистрации нужно:

    • описать своё приложение и желаемое название (для записи информации, которая будет показываться конечным пользователям),
    • перечислить необходимые роли пользователей (этот список можно будет менять в дальнейшем),
    • указать тип приложения:

      confidential
      клиенту требуется секрет для запуска процесса логина,
      public
      клиенту не требуется секрет для запуска логина,
      bearer-only
      клиент не запускает логин,
    • какие из следующих вариантов логина будут использоваться:

      • Authorization Code Flow,
      • Implicit Flow,
      • Direct Access Grants (клиент напрямую передаёт логин/пароль пользователя серверу авторизации),
      • Service Accounts (клиент может получить токен, связанный не с каким-то пользователем, а с клиентом),
    • корневой URL и допустимые URL для перенаправления, допустимые Web Origins для заголовков CORS;
  • после регистрации разработчик получает пару client-id и client-secret, которые может использовать для авторизации клиента на сервере авторизации. Допустимы и другие способы авторизации: подписанный (открытым ключом либо секретом клиента) JWT, клиентские X.509 сертификаты.

Например, для данного приложения информация при регистрации выглядела бы так:

  • Название: s.chgk.me.
  • Описание: Сокращение ссылок - демо для сервера авторизации.
  • Роли: администратор (роль обычного пользователя и роль гостя есть у всех клиентов).
  • Тип приложения: confidential
  • Варианты логина: Authorization Code Flow, Implicit Flow.
  • Корневой URL: https://s.chgk.me.
  • URL для перенаправления:

    • https://s.chgk.me/auth/*
    • http://localhost:4102/auth/*
  • Web Origins: https://s.chgk.me.

Процесс авторизации (в варианте Authorization Code Flow)

  • Клиент выполняет запрос к Authorization Endpoint (см. redirectAS)
  • AS идентифицирует пользователя и возвращает Authorization Code, выполняя вызов страницы-callback
  • Клиент выполняет запрос к Token Endpoint, указав Authorization Code, и получает Identity Token и Access Token (см. callbackAS)
  • На нашей странице callback мы просто сохраняем нужную информацию в виде (подписанного, но не зашифрованного) JWT-токена в local storage (см. cbPage). По-хорошему, схема должна быть сложнее
  • Клиент может выполнить запрос к Token Introspection Endpoint, указав секрет клиента и Access Token, и получить информацию о пользователе, которой нет в Identity Token (см. getUserInfo)
  • В нашем случае при использовании API требуется передавать заголовок Authorization с JWT Token
Synopsis

Получение информации о сервере авторизации

redirectUri Source #

Arguments

:: Config

Конфигурация приложения, чтобы получить базовый URL

-> ByteString

Адрес для callback-запросов

Для работы OpenID Connect необходимо предусмотреть запрос, который будет выполнять сервер авторизации после попытки логина.

В нашем приложении мы генерируем адрес этой страницы в типобезопасном виде, и он не рассинхронизируется с остальным API

В нашем случае адрес выглядит так: https://s.chgk.me/auth/cb

initializeOIDC Source #

Arguments

:: Config

Конфигурация приложения, чтобы при помощи client-id, секрета и redirectUri инициализировать библиотеку

-> CookieSettings

Настройки для Cookies, используемых при авторизации и для защиты от XSRF-атак

-> JWTSettings

Настройки для JWT-токенов, используемых при авторизации в приложении

-> IO OIDCEnv

Информация, необходимая для работы клиента OIDC

Для работы протокола необходимо знать разные endpoint на AS, такие как Authorization Endpoint, Token Endpoint и т.п. Их значения можно заполнить вручную, а можно всю эту информацию получить при помощи OpenID Connect Discovery в виде JSON из https://auth.chgk.me/auth/realms/chgk/.well-known/openid-configuration

Как правило, библиотекам достаточно указать часть до .well-known.

В нашем приложении мы выполняем этот процесс, инициализируем менеджер соединений и сохраняем необходимую информацию в OIDCEnv.

Реализация авторизации в приложении

authServer Source #

Arguments

:: OIDCEnv

Информация, необходимая для работы клиента OpenID Connect

-> Server AuthApi

Реализация API для авторизации

Реализация API для авторизации состоит из двух частей:

  • реализации запроса переадресации redirectAS;
  • реализации callback-запроса callbackAS.

Перенаправление на сервер авторизации

redirectAS Source #

Arguments

:: OIDCEnv

Информация для работы клиента OpenID Connect

-> Server RedirectAS

Реализация запроса на переадресацию к серверу авторизации

Выполняется переадресация к Authorization Endpoint: https://auth.chgk.me/auth/realms/chgk/protocol/openid-connect/auth

Запрашиваются требования (claim): openid, profile, email

В качестве состояния (state) используется случайная строка, сгенерированная при помощи genRandomBS.

Обработка callback-запроса

data AuthInfo Source #

Информация, возвращаемая в Identity Token

Constructors

AuthInfo 

Fields

Instances
Eq AuthInfo Source # 
Instance details

Defined in Auth

Read AuthInfo Source # 
Instance details

Defined in Auth

Show AuthInfo Source # 
Instance details

Defined in Auth

Generic AuthInfo Source # 
Instance details

Defined in Auth

Associated Types

type Rep AuthInfo :: * -> * #

Methods

from :: AuthInfo -> Rep AuthInfo x #

to :: Rep AuthInfo x -> AuthInfo #

FromJSON AuthInfo Source # 
Instance details

Defined in Auth

Methods

parseJSON :: Value -> Parser AuthInfo

parseJSONList :: Value -> Parser [AuthInfo]

ToJSON AuthInfo Source # 
Instance details

Defined in Auth

Methods

toJSON :: AuthInfo -> Value

toEncoding :: AuthInfo -> Encoding

toJSONList :: [AuthInfo] -> Value

toEncodingList :: [AuthInfo] -> Encoding

type Rep AuthInfo Source # 
Instance details

Defined in Auth

callbackAS Source #

Arguments

:: OIDCEnv

Информация для работы клиента OpenID Connect

-> Server CallbackAS

Реализация callback-запроса

Выполняются следующие действия:

  • проверяется параметр error, если он есть - при логине произошла ошибка, возвращаем 401;
  • проверяется параметр code, если его нет - возвращаем 401;
  • выполняется запрос к Token Endpoint https://auth.chgk.me/auth/realms/chgk/protocol/openid-connect/token. При запросе передаётся код авторизации. В ответ получаем идентификационный токен и токен доступа;
  • декодируется идентификационный токен, если это не удалось - возвращаем 401;
  • проверяется, что почтовый адрес верифицирован, иначе - 401;
  • создаётся JWT-токен с информацией о пользователе (см. User) и возвращается HTML-страница, на которой этот JWT-токен сохраняется в локальное хранилище и происходит переадресация в корень сайта.

cbPage Source #

Arguments

:: Text

JWT-токен для авторизации

-> User

Информация о пользователе

-> [Text]

Список ролей пользователя

-> Html

Страница, которая сохраняет информацию и перенаправляет в корень

Сохраняются следующие ключи в локальное хранилище:

token
JWT-токен с информацией о пользователе;
name
имя пользователя;
mail
электронная почта пользователя;
roles
список ролей через запятую.

Получение дополнительной информации о токене и пользователе

getUserInfo Source #

Arguments

:: OIDCEnv

Информация для работы клиента OpenID Connect

-> User

Информация о пользователе (в ней сохранён проверяемый токен в поле token)

-> IO (Either Text [Text])

Текст ошибки, либо список ролей пользователя на клиенте

Выполняется запрос к Token Introspection Endpoint, токен проверяется функцией verifyUserInfo и возвращается список ролей пользователя на клиенте (или ошибка)

Для интроспекции нужно сделать POST-запрос к https://auth.chgk.me/auth/realms/chgk/protocol/openid-connect/token/introspect.

Запрос должен быть защищён Basic Auth с именем пользователя - идентификатором клиента и паролем - секретом клиента.

В теле запроса должен быть токен (переданный как форма, т.е. token=eyJhbG...z0a__w)

verifyUserInfo Source #

Arguments

:: IssuerLocation

Адрес сервера авторизации, должен совпадать с iss

-> Text

Идентификатор клиента, должен совпадать с aud и azp

-> POSIXTime

Текущее время, должно быть раньше, чем момент времени exp

-> Value

Проверяемый токен

-> Either Text [Text]

Сообщение об ошибке либо список ролей

Токен проверяется, как указано в п.3.1.3.7 спецификации OpenID Connect

В частности, проверяются поля iss, aud, azp, exp.

Кроме того, проверяется, что токен действует (поле active).

Если токен проходит проверку, то возвращается список ролей из поля [resource_access][s-chgk-me][roles].