Пишем код

Заметки о .net разработке

OAuth в SPA или неожиданные сложности интеграции логина через соцсети в React с Asp.Net Core

without comments

Это история про то, как казалось бы типичная задача интеграции входа через соц.сети в React/Asp.Net Core приложении может превратиться в длинную сагу и закончиться open-source библиотекой :)

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


В Asp.Net Core существует замечательная встроенная интеграция с внешними провайдерами аутентификации (OAuth/OpenId и прочее нестандартное), а также сторонние плагины, поддерживающие аутентификацию даже через VK. Однако весь этот механизм подразумевает, что у вас обычное server-side приложение (с forms-авторизацией), и никаких собственных access_token’ов, которые привычны в SPA вам генерироваться не будет.

Вот вот мне и загорелось желание воспользоваться всем этим огромным количеством готовых решений и подружить его с SPA. Как схема работы должна выглядеть в идеале? Согласно AuthCode Flow (который считается рекомендуемым для использования в SPA), это должно выглядеть примерно так:

SPA получает AuthCode у стороннего провайдера и передает его на бэкэнд приложения. Бэкэнд проверяет верность кода, ищет этого пользователя в своей БД (и создает, если требуется) и возвращает на фронтенд access_token, с которым и происходят все дальнейшие запросы.

Для первого шага (получения AuthCode) в SPA существуют готовые реализации в виде, например, реакт-компонентов. Но под каждого OAuth-провайдера они разные, и подбор и настройка могут отнять достаточно много времени. После интеграции написанной библиотеки IdentityOAuthSpaExtensions (и настройки бэкэнд-части согласно инструкциям от майкрософта), запрос AuthCode из SPA будет состоять из двух частей:

  1. Создание обработчиков и подписка на события:
  2.     window.addEventListener("message", this.oAuthCodeReceived, false);
        function oAuthCodeReceived(message) {
            if (message.data && message.data.type === 'oauth-result') {
                if (data.code) {
                    externalAuthSuccess(data.provider, data.code);
                } else {
                    externalAuthError(data.provider, data.error, data.errorDescription);
                }
            }
        }
        function externalAuthSuccess(provider, code) {
            alert(`Provider: ${provider}, code: ${code}`);
        }
        function externalAuthError(provider, error, errorDescription) {
              alert(`Provider: ${provider}, error: ${error}, ${errorDescription}`);
        }
    
  3. Старт процедуры авторизации:
   window.open(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/external-auth/challenge?provider=${provider}`, undefined, 'toolbar=no,menubar=no,directories=no,status=no,width=800,height=600');

В результате этого ваше SPA получит AuthCode стороннего провайдера. В дальнейшем с ним можно делать что угодно ( :)), но в нашем случае, мы хотим получить access_token от нашего бэкэнда, чтобы в дальнейшем все http вызовы совершать с этим access_token’ом. Для этого в библиотеке существует возможность проверки AuthCode («), а также (рекомендуемая) интеграция с IdentityServer в виде extension grant’a. Описание интеграции очень подробно описано на гитхабе

Итоговая схема выглядит как-то так:

Из основных плюсов библиотеки:

  1. Единая точка входа и общий интерфейс интеграции любых Auth-провайдеров (не нужно менять SPA, меняется лишь одна переменная — имя провайдера — при открытии URL авторизации)
  2. Использование стороннего кода для взаимодействия с OAuth (саму библиотеку не придется обновлять, если в сторонних OAuth что-то изменится).
  3. Добавление новых провайдеров происходит стандартным способом (по инструкции для server-side приложений) и не требует модификации самой библиотеки

Пользуйтесь, задавайте вопросы и рассказывайте об успешных сценариях внедрения!

Written by Shaddix

Март 16th, 2019 at 4:55 дп

Второй митап .Net разработчиков TomskDotNet#2

without comments

Наш второй митап по .NET состоялся!

Немного митапной статистики:

  • 3 докладчика;
  • 80+ участников;
  • 100 печенек и целая куча других вкусностей;
  • 30 банок колы и других напитков;
  • 5 кружек с лабиринтами;
  • 2 отлично проведенных часа жизни и многое многое другое…

Фоточки и всё такое можно посмотреть в нашей группе ВК.

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

Вот они:

Присоединяйтесь к нам 31 января, на TomskDotNet #3!

Written by Shaddix

Март 9th, 2019 at 3:15 дп

Posted in семинары

Доклад про Entity Framework Core на TomskDotNet#1

without comments

В конце ноября мы открыли сезон митапов TomskDotNet, и на первом из них я рассказал про особенности Entity Framework Core, типичные ошибки при работе с этой ORM, оптимизацию запросов и мониторинг.

Презентацию можно посмотреть ниже или скачать по ссылке.


С удовольствием приглашаю вас на наши последующие митапы (ближайший из которых — 20 марта).

Written by Shaddix

Март 6th, 2019 at 2:51 дп

Митап .Net разработчиков TomskDotNet#1

without comments

29 ноября в Точке Кипения состоялся первый томский митап .NET программистов :)
Организацией, рекламой, фирменными печеньками, сладостями, кока-колой и прочими безобразиями занималась компания МЦЦ Томск (в том числе и я :)).

Что же всё таки было? А вот что:

Видео и презентации по ссылкам выше :)

Всем знаний — и присоединяйтесь к нам 31 января, на TomskDotNet #2!

Written by Shaddix

Декабрь 4th, 2018 at 8:39 пп

Доклад про WebAssembly на Городе IT

without comments

10 сентября выступил на секции Enterprise конференции Город IT с докладом по WebAssembly.
С моей точки зрения, WebAssembly — это очень перспективное направление, хоть и не готовое к продакшену прямо сейчас. Но нам — разработчикам — просто необходимо внимательно следить за технологиями, которые уже в ближайшее время могут радикально изменить ситуацию в мире фронтэнда.
В докладе рассказывал про появление и развитие WebAssembly, языки и фреймворки, которые его используют, и показал возможности применения Майкрософтоской библиотеки Blazor для разработки SPA с использованием любых существующих .net библиотек (netstandard).
Спасибо всем участникам конференции за интерес и плодотворную дискуссию!

Презентация — уже сегодня на Slideshare, надеюсь на скорое появление видео (ждём организаторов!)
WebAssembly.pptx

Frontend в enterprise или сказка про WebAssembly from ArturDr

Written by Shaddix

Ноябрь 12th, 2018 at 10:35 дп

OpenApiGenerator: fixing multiple file upload for JS clients

without comments

Remember, I love swagger! And since we have it anyway, it’s kinda dumb to write requests to that API manually.
Why would anyone want to come up with something like axios.get(‘/user/123/payments?fromDate=2018-01-01’) without intellisense, type-checking and compile-time verifications? What if someday you change that API’s route?

OpenApiGenerator is a great solution to this. You could easily get typed-clients for your API in almost any language, and make requests as easy as UserApi.get(123, dateFrom).
But that’s a well-known thing, let’s get to the point of this article :)
OpenApiGenerator plays well with almost anything, but file arrays. File arrays were introduced to OpenApi recently, so not all the tools support them yet.
So, let’s say you have a backend method that accepts an array of files (files will be passed with the same name):

[HttpPost()]
public IActionResult UploadFiles(IFormFile[] files)

OpenApiGenerator will provide you with a proper JS method, but it won’t work and will generate something like:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryIk1aWkovTmf7LSJX

------WebKitFormBoundaryIk1aWkovTmf7LSJX
Content-Disposition: form-data; name="files"

[object File],[object File]
------WebKitFormBoundaryIk1aWkovTmf7LSJX--

Of course, that isn’t what we want. However, it’s quite easy to monkey-patch the generated ApiClient to achieve what we want. I know monkey-patching is dirty and you should probably fix this with inheritance&overriding, but I’ll leave this to you :) So, here’s the patch:

//this is to make ApiClient correctly handle array of files
const oldBuildCollectionParam = ApiClient.instance.buildCollectionParam;
ApiClient.instance.buildCollectionParam = function(param, collectionFormat) {
    if (param == null) {
        return null;
    }
    if (param.length > 0) {
        if (param[0] instanceof File) {
            return param;
        }
    }
    return oldBuildCollectionParam.call(this, param, collectionFormat);
};

Written by Shaddix

Сентябрь 5th, 2018 at 11:58 дп

Posted in javascript

How to make Swagger correctly handle file uploads (IFormFile) in Asp.Net Core

without comments

I love Swagger (OpenAPI). It’s a really nice way to share your API definitions, and it is so easy to integrate it into ASP.Net Core applications, that there’s literally no excuse not to use it.
The only glitch in Swagger’s integration is the lack of IFormFile support. So, if you’d like to upload a file you typically write the following action:

[HttpPost()]
public IActionResult UploadFile(IFormFile file)

And that’s how it’s gonna be displayed in Swagger-UI (the real problem is, of course, in wrong swagger.json definition). As you could see, there’s everything but an ability to upload a file (actually, Swagger exposed all properties of IFormFile, which is completely useless).
Swagger

There’s a nice discussion on Stackoverflow regarding this subject, and a couple of answers that solve the problem either via hardcoding or putting an attribute on an action. That didn’t sound like a generic solution for me, so I went down and implemented an Operation Filter that fixes the issue without any additional work.
The integration is quite simple (as with any other Swagger filter)

services.AddSwaggerGen(c => c.OperationFilter<FileUploadOperation>());

And here’s the actual filter. It handles IFormFile action parameters, IFormFile within other classes and even arrays(or any IEnumerables) of IFormFile.

    /// <summary>
    /// adds an ability to upload files via Swagger (and autogenerated js client)
    /// </summary>
    public class FileUploadOperation : IOperationFilter
    {
        public void Apply(Operation operation, OperationFilterContext context)
        {
            if (context.ApiDescription.HttpMethod != "POST")
                return;

            if (operation.Parameters == null)
                operation.Parameters = new List<IParameter>();

            var isFormFileFound = false;

            //try to find IEnumerable<IFormFile> parameters or IFormFile nested in other classes
            foreach (var parameter in operation.Parameters)
            {
                if (parameter is NonBodyParameter nonBodyParameter)
                {
                    var methodParameter =
                    context.ApiDescription.ParameterDescriptions.FirstOrDefault(x => x.Name == parameter.Name);
                    if (methodParameter != null)
                    {
                        if (typeof(IFormFile).IsAssignableFrom(methodParameter.Type))
                        {
                            nonBodyParameter.Type = "file";
                            nonBodyParameter.In = "formData";
                            isFormFileFound = true;
                        }
                        else if (typeof(IEnumerable<IFormFile>).IsAssignableFrom(methodParameter.Type))
                        {
                            nonBodyParameter.Items.Type = "file";
                            nonBodyParameter.In = "formData";
                            isFormFileFound = true;
                        }
                    }
                }
            }



            //try to find IFormFile parameters of method
            var formFileParameters = context.ApiDescription.ActionDescriptor.Parameters
                .Where(x => x.ParameterType == typeof(IFormFile)).ToList();
            foreach (var apiParameterDescription in formFileParameters)
            {
                operation.Parameters.Add(new NonBodyParameter
                {
                    Name = apiParameterDescription.Name,
                    In = "formData",
                    Description = "Upload File",
                    Required = true,
                    Type = "file"
                });
            }

            if (formFileParameters.Any())
            {
                foreach (var propertyInfo in typeof(IFormFile).GetProperties())
                {
                    var parametersWithTheSameName = operation.Parameters.Where(x => x.Name == propertyInfo.Name);
                    operation.Parameters.RemoveRange(parametersWithTheSameName);
                }
            }


            if (isFormFileFound)
                operation.Consumes.Add("multipart/form-data");
        }
    }

Written by Shaddix

Август 28th, 2018 at 10:48 дп

Using Mini-Profiler with Angular and HttpClient

without comments

Performance is essential for every web app, so profiling is a must. And there’s no better tool to monitor and profile your web app than Mini-Profiler from StackExchange. It’s simple, easily integratable and provides the most important profile metrics such as request duration and SQL queries.

Our typical Web SPA setup in Rubius is based on ASP.Net Core backend and Angular frontend. So we started integrating MiniProfiler into the stack, but it wasn’t that straightforward.

The thing is, MiniProfiler works perfectly with classic pages and jquery ajax calls, but fails to display any information on Angular http requests, which makes it barely usable in SPA.
To overcome it, there is a perfect post from Georg Dangl on how to make Mini-Profiler work with Angular if you’re using HttpModule, so go read Georg’s post and gist, it’s for you :)

However, in 4.3 Angular introduced HttpClientModule as a new way to talk to the backend API (replacing old HttpModule) and once we started to migrate to it we had the same issue again. There were no information about ajax requests.
So, I sat down and ported Georg’s gist to an HttpClient. So if you care about your performance and SQL queries, go grab it!

Written by Shaddix

Февраль 17th, 2018 at 11:33 дп

Posted in .net,angular,web

Семинар по ASP.Net Core в Точке Кипения

without comments

14 декабря вместе с коллегой Антоном Финько выступали в Точке Кипения с семинаром по ASP.Net Core.

y_eKgPHhHbs
Очень понравилась сама площадка — Точка Кипения — это отличное место, просторный зал, огромный экран и все пришедшие 60 человек там отлично разместились (и даже если было бы вдвое больше — всё равно всем было бы удобно :)). Фотографии не передадут всего комфрота и уюта (и кофе-брейка с плюшками), но покажут, насколько было хорошо:
4kZgU9Ke01U

nZQgjVYFvvE

F_UDmu7bIW8

Это было первое IT-мероприятие в Точке, и я считаю, оно прошло отлично! Небольшой 40-минутный доклад про теоретические основы и большой практический опыт, и более чем получасовая дискуссия после. Очень порадовал обмен мнениями и опытом использования от многих присутствовавших.

Ну а тем, кто не пришел — видео и презентация ниже :)

AspnetCore.pptx

ASP.Net Core from ArturDr

До встречи на следующих семинарах!

Written by Shaddix

Февраль 10th, 2018 at 11:58 пп

Доклад по кроссплатформенной мобильной разработке на DevPro Красноярск

without comments

25 ноября выбрался в Красноярск, чтобы посетить местную конференцию D2D DevPro.
Организовывали её наши коллеги и партнеры из Аспирити, и получилось очень неплохо. 300+ человек, 3 параллельных секции — это первая IT конференция такого масштаба в Красноярске!

С удовольствием выступил и рассказал о подходах к кроссплатформенной разработке, про свой любимый Xamarin, про набирающий популярность React Native. Демо-показ возможностей React Native / Xamarin Forms / Xamarin на сходных примерах очень удачно продемонстрировал development experience при использовании кроссплатформенных фреймворков.

С видео наши красноярские друзья, к сожалению, подкачали, поэтому выкладываю видео с «репетиции» доклада, которая прошла в Томске за пару недель до DevPro :) А вот презентация — уже с самого Красноярска, доработанная и улучшенная.

Слайды:
Cross Platform.pptx

Кросс-платформенная мобильная разработка from Artur Drobinskiy

Written by Shaddix

Февраль 5th, 2018 at 12:27 дп

Posted in семинары