How to use MSAL for signup in a web application 
Published: 2019-10-03 21:12:06

MSAL (Microsoft authentication library) is the most modern way to connect with servises, that Misrosoft provides. Oh boy... I will write this text till morning, if I will continue in English... Could you, guys, excuse me, if I use my native Russian to finish the article? Thank you! I was shure, that there is most frendly community in the world! To be honest, we all read such texts only for code examples, do we? So, I guarantee, that code will not be written in Russian (but in CoffeScript, and one can't say, what is worse for reading :)

Так вот, [MSAL](https://github.com/AzureAD/microsoft-authentication-library-for-js) - библиотека, которую можно использовать для подключения к сервисам Microsoft. И даже не можно, а нужно, поскольку мелкомягкие отказываются от более ранних своих технологий аутентификации (таких, как ADAL) в пользу этой, свежей. MSAL интегрирована в MS Graph - систему, объединяющую все сервисы Microsoft.

Теперь о мотивации. Зачем мне, убежденному и многолетнему стороннику Linux и человеку, настроенному по отношению к технологиям Microsoft довольно скептически, потребовалась одна из них? По простой причине: я не хочу создавать на своем сайте, посвященном онлайн-обучению, собственную службу аутентификации. Всё, что мне нужно - отправить зарегистрировавшемуся пользователю письмо с приглашением. Для этого  не нужно запрашивать логин и пароль, прочие данные. Можно просто воспользоваться готовыми сервисами (Google, Microcoft, Яндекс, ВКонтакте) и, "пропустив" нового пользователя через них, получить адрес электронной почты. Вполне типичный по нынешним временам подход. 

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

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

``` html
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.1.2/js/msal.min.js"></script>
```  

Кстати, как я уже отметил, библиотека интенсивно развивается и есть уже как минимум версия `1.1.3`. За нее и более свежие не ручаюсь. Буду рассказывать о коде, который у меня действительно работает.

Прежде чем продемонстрировать возможности MSAL, приведу два пояснения. Во-первых, я мало пользуюсь современными фреймворками вроде React, Angular или Vue (этим почаще). Веб приложения, которые я создаю, достаточно просты, чтобы в них можно было обойтись средствами "ванильного" JavaScript, а посему использую CoffeeScript, позволяющий писать более лаконичный код. Для бэкенда, конечно же, не пишу на нем, там не обойтись без возможностей ES6. Но на фронтенде CoffeeScript до сих пор остается отличным инструментом, ускоряющим разработку. (Мне даже кажется, что такие возможности ES6, как функции-стрелки и некоторые другие позаимствованы как раз из CoffeeScript, где появились задолго до 2015 года). Так вот, как я и предупреждал: тех, кого не отпугнул русский язык, на котором написана данная статья, ждет еще одно испытание:

``` coffeescript
switch @.location.pathname
    when '/signup-msal.html'
        setTimeout ()->
                    req = scopes: ['user.read']
                    msal = new Msal.UserAgentApplication
                        auth:
                            clientId: '852e6552-blah-blah-blah'
                            redirectUri: 'https://blah-blah.ru/json/invite-with-msal'
                    msal.handleRedirectCallback (err, res)->
                        msal.acquireTokenSilent req
                            .then (res)->
                                headers =  new Headers 'Authorization': "Bearer #{res.accessToken}"
                                options = method: "GET", headers: headers
                                fetch 'https://graph.microsoft.com/v1.0/me', options
                                    .then (res)-> res.json()
                                    .then (obj)->
                                         query = "to=#{encodeURIComponent obj.userPrincipalName}&name=#{encodeURIComponent obj.displayName}"
                                         window.location = "/json/invite-with-email?#{query}"
                    if not msal.getAccount()
                        msal.loginRedirect req
                        return
            , 1000
    when '/json/invite-with-msal'
        if not /token/.test @.location.hash
            alert 'Авторизация через сервис Microsoft не удалась.'
            @.location = '/signup.html'
            return
        new Msal.UserAgentApplication
            auth:
                clientId: "852e6552-blah-blah-blah"
                redirectUri: "https://blah-blah.ru/json/invite-with-msal"

```

На этом можно было бы и остановиться: подставьте в этом коде свой идентификатор приложения вместо `852e6552-blah-blah-blah`, свой адрес сервера вместо `blah-blah.ru` и получите работающую систему, выясняющую email регистрирующегося пользователя и отправляющую ему письмо с приглашением воспользоваться вашими услугами. Однако пояснения, конечно же, нужны, поскольку сам я, нагуглив массу примеров "работающего кода", смог заставить его действительно работать с большим трудом. Не думаю, что причина в моей умственной неполноценности. Скорее в недостатке документации, который я и восполняю этой статьей.

Так вот. Зачем в этом скрипте две (на самом деле три) "точки входа": 

```
    when '/signup-msal.html'
```

и 


```
    when '/json/invite-with-msal'
```

?

А это "чтобы два раза не бегать". Один и тот же скрипт используется на двух веб-страницах. Первая (`/signup-msal.html`) - обычная статическая страница. С нее незалогиненный (простите мне этот русский неологизм) пользователь перенаправляется на какие-то специальные сайты, где выясняется 

* является ли он зарегистрированным  пользователем  принадлежащих Microsoft сервисов, таких, как Skype, Outlook и т.д. Если нет - предложат зарегистрироваться;
* выполнил ли зарегистрированный пользователь вход. Если нет - предложат залогиниться;
* доверяет ли пользователь вашему веб-приложению. Он ведь пользуется им впервые, поэтому должен подтвердить, что согласен с тем, чтобы вашему сайту была предоставлена некоторая информация о нем.

Пройдя всю эту цепочку, пользователь направляется сервисами Microsoft на ваш сайт, в нашем примере по адресу `/json/invite-with-msal`. Тут ситуация посложнее. Мы тоже загружаем библиотеку и создаем инстанс MSAL, но, в отличие от `/signup-msal.html`, не пользуемся явно ее возможностями, а позволяем ей сделать всё, что она считает нужным. А делает она, судя по всему (я точно не выяснял, но догадываюсь), следующее: из url (`location.hash`) извлекает токен и прочую  полезную для себя информацию, сохраняет ее в `sessionStorage`, в общем делает что-то такое, после чего запрос адреса электронной почты становится возможным. Если заглянуть после всей этой бурной деятельности в  `sessionStorage`, то можно увидеть там множество длинных и сложных записей, что свидетельствует о том, что аутентификация закончилась успешно. Кстати, на период отладки можно удалять записи из  `sessionStorage` и делать таким образом пользователя вновь "незарегистрированным".

Нетрудно заметить, что на первом этапе мы имеем дело со статическим `html`-файлом, во втором - с какой-то активной частью бэкенда (в моем случае она написана на JavaScript для среды NodeJS). Здесь удобный момент убедиться, что запрос пришел действительно от серверов Microsoft (`login.microsoftonline.com`, `account.live.com` и т.п.), а не от хакеров, пытающихся взломать ваш сайт. Т.е. запросы, приходящие на этот url не с серверов Microsoft, нужно без колебаний отфутболивать, например, [сюда](https://www.youtube.com/watch?v=s23OLr0ZNPI) (русские поймут :). 

Далее, поняв, что запрос пришел с серверов Microsoft, готовим третью точку входа (`/json/invite-with-email`) к приему письма, например, разрешив принимать на нее запросы в течение следующих 10 секунд. Ну, а тем временем скрипт на странице `/json/invite-with-msal`, сделав свои дела и набив `sessionStorage` необходимой требухой, возвращает управление обратно, к началу процесса, т.е. на `/signup-msal.html`. Но там ситуация уже изменилась. Если в первый раз браузер не был авторизирован, то теперь он получил необходимые для получения информации о пользователе полномочия (токен доступа). Он уже не перенаправляется по цепочке серверов Microsoft, а делает прямой запрос на `https://graph.microsoft.com/v1.0/me`, получает необходимую информацию о пользователе (в том числе email) и отправляет запрос с персональными данными на `/json/invite-with-email`. И вот уже здесь происходит занесение данных о пользователе в БД сервера, отправляется ему электронное письмо-приглашение и т.п.

Работающий пример можно посмотреть [здесь](https://js-invite.ru).

Это моя первая статья на `dev.to`. Я не знаю, много ли здесь русскоязычных пользователей, но если такой формат (текст на русском) окажется востребованным, я готов написать такие же материалы про аутентификацию средствами Google и Яндекс. А английским я не владею настолько, чтобы связно излагать на нем длинные рассуждения, уж извините, или, как у нас тут говорят в последние годы, сорян (от англ. sorry). Читать на английском - читаю, могу даже немного говорить и писать, но практики общения мало, поэтому знание английского - мое слабое место.