Всегда хотел написать веб приложение в котором клиент полностью отделен от сервера, находится на другом домене и с сервером только по REST общается. Встрял на одной проблеме. Довольно интересной.
Решил Я попробовать сделать следущую архитектуру. Веб приложение находится на основном домене, а сервер с логикой располагается на другом. Технологии позволяют: Cross-Origin-Resource-Sharing уже в браузерах. Механизм такой: приложение запрашивает сервер на другом домене, а он отвечает с заголовками
Access-Control-Allow-Origin: http://your-webapp.com
Браузер не ругается. Полет нормальный.
Следующий этап приложения - это авторизация. Как мы знаем, в качестве идентификатора пользователся в вебе используются Cookies. И, вроде они нам подходят. Согласно спецификации (link), для использования идентификаторов (credentials) при работе с CORS, нам нужно сделать
invocation.open('GET', url, true);
invocation.withCredentials = true; # выставить флаг на клиенте
invocation.onreadystatechange = handler;
invocation.send();
Access-Control-Allow-Credentials: true # добавить заголовок в ответ на сервере
С этого момента становится возможным передача Cookie / Set-Cookie заголовков. В ином случае их отрежет браузер. Теперь должно работать. И, даже работает. Но не везде. Далеко не везде.
Third-Party Cookies
Куки третьей стороны ;) link. В двух словах: браузер позволяет установить куки для домена, отличного от домена в адресной строке. Это может быть сделано, в результате запроса картинки/скрипта. TP Cookies используются компаниями, которые хотят следить за пользователями в интернетах. И да-да. Кнопка “+1” и “like” тоже следит за нами. Именно с помощью third-party-cookies. А потом удивляешься, почему после того, как ты посмотрел сайт про ремонт, facebook начал тебя объявлениями тематическими заваливать.
Intersection
Так что же произойдет, если мы будем использовать CORS при включенной блокировке Third-Party Cookies? Я попробовал. И браузер отказался принимать Cookies. Вот она - проблема!
Firefox && Safary
По умолчанию, Third-Party-Cookies запрещены в Firefox & Safary. Как же без этого работают встраиваемые виджеты!? Например, виджет Disqus, который можно наблюдать под статьей. Ха-ха. А ведь он не работает. Точнее, работает, но не полностью. Не работает OAuth авторизация. Похоже, Disqus об этом знает (link), но ничего сделать не может.
Пичаль
Удостоверившись в данном поведении Я расстроился и написал статью до текущего параграфа. Но, желание отделить клиент и сервер не уменьшилось. И Я решил поискать архитектурные решения среди современных веб сервисов.
Зашел в linkedIn - ужаснулся какая кака в коде. Решения не нашел. Зашел в Yammer. И нашел! Оказалось, что Яммер использует cometd based сервис, который находится на домене rt-123.rt.yammer.com. И использует CORS и Cookie. И… невероятно… такая связка работает в браузере с включенной блокировкой third-party-cookies.
Эксперимент
Я не понял, почему работает у них, а у меня нет. Но, Я был намерен это узнать =) Догадки, которые были на тот момент:
- SSL - Возможно, хитрые браузеры не считают за third-party домены с идентичным wildcart SSL сертификатом?
- Я опять продолбал важный параграф в спецификации CORS
- Хаки со стороны Yammer
Больше всего надежды было на SSL. А зря. Как оказалось, wildcard сертификаты распространяются только на поддомены первого уровня. А тут rt-123.rt.yammer.ru - второго. Не работает. И правда, проверив сертификат rt домена, Я обнаружил, что он другой.
Хаков со стороны Yammer-a обнаружено не было. Спецификацию Я перечитал. И всеже оно заработало.
Не third-party
Оказалось, что браузер блокирует запросы к поддоменам по причине cross-origin, однако не считает их third-party. Ох, сложно. Сейчас объясню.
Возьмем два домена
yourdomain.com
api.tapcat.net
и tapcat.net
- основной домен, с которого будем делать запросы.
При использовании CORS мы явно можем разрешить доступ к выбранным доменам. При разрешенных third-party cookies работа и с одним и с другим доменом не доставит хлопот.
А вот, если TPC заблокированы….
В этом случае, оказалось, что браузер срежет куки для домена yourdomain.com
, но позволит проставить cookie для домена api.tapcat.net
.
Глубина поддоменов не учитывается и все работает даже для: dev.api.tapcat.net; a.b.c.d.tapcat.net
.
Получается, оно работает потому что текущий Origin содержится в данном домене!?
I need proof
Решение работает в современных браузерах. Данное поведение должно быть стандартизировано! Ищем стандарт! Нам подходят документы:
Первый говорит, что есть такие third-party cookies, которые user agent может блокировать. А как отличить одни Cookie от других - не сказано. Однако, есть интересный параграф:
The user agent will reject cookies unless the Domain attribute
specifies a scope for the cookie that would include the origin
server. For example, the user agent will accept a cookie with a
Domain attribute of "example.com" or of "foo.example.com" from
foo.example.com, but the user agent will not accept a cookie with a
Domain attribute of "bar.example.com" or of "baz.foo.example.com".
Но, вроде, не про то. У нас то сам api.tapcat.net хочет проставить Сookie для api.tapcat.net.
Второй документ находится в состоянии Candidate Recommendation и ничего про нашу ситуацию не говорит.
А про механизм определения third-party-cookies - вообще засада. Нигде нет его описания. Помогите мне его найти! 4 дня искал и не нашел. Даже смотрел исходный код Firefox. Но, ссылок на стандарт там нет. Поиски продолжаются.
И, да. В результате удалось отделить статику от сервера с бизнес логикой на уровне доменов. Как Я и хотел. ;)