{"id":14902,"date":"2022-02-21T12:03:42","date_gmt":"2022-02-21T11:03:42","guid":{"rendered":"https:\/\/www.centigrade.de\/blog\/how-to-engage-graphql-net-and-react-together-part-ii-veda-versum-backend-authentication\/"},"modified":"2022-05-16T16:03:52","modified_gmt":"2022-05-16T14:03:52","slug":"how-to-engage-graphql-net-and-react-together-part-ii-veda-versum-backend-authentication","status":"publish","type":"blog","link":"https:\/\/www.centigrade.de\/de\/blog\/how-to-engage-graphql-net-and-react-together-part-ii-veda-versum-backend-authentication\/","title":{"rendered":"GraphQL, .Net und React miteinander verbinden &#8211; Teil II: Veda Versum backend authentication."},"content":{"rendered":"<blockquote>\r\n<p>&nbsp;<\/p>\r\n<p><em>&#8222;If you optimize everything, <\/em><em>you will always be unhappy&#8220; &#8211; \u00a0<\/em><a href=\"https:\/\/en.wikipedia.org\/wiki\/Donald_Knuth\">Donald Knuth<\/a><\/p>\r\n<\/blockquote>\r\n<p>Mit einem guten Tool k\u00f6nnen Sie Aufgaben effektiv erledigen und dabei Spa\u00df haben. Es macht aber doppelt so viel Spa\u00df, das Werkzeug selbst zu entwickeln \ud83d\ude42<\/p>\r\n<p>Hallo, hier ist wieder <a href=\"https:\/\/www.centigrade.de\/de\/unternehmen\/team#mikhail.shabanov\">Mikhail<\/a> von Centigrade. Im Blogartikel m\u00f6chte ich weiter unser Tool aufbauen &#8211; eine Wissensdatenbank namens <strong>Veda Versum<\/strong>. Der Artikel ist der zweite der Serie &#8222;GraphQL, .Net und React miteinander verbinden&#8220;. Im <a href=\"https:\/\/www.centigrade.de\/de\/blog\/wie-man-graphql-net-und-react-miteinander-verbindet-teil-i\/\">vorherigen Artikel<\/a> haben wir unsere Zielanwendung, Anforderungen und Architektur definiert. Wir haben .Net 6 f\u00fcr das Backend mit der <a href=\"https:\/\/github.com\/ChilliCream\/hotchocolate\">Hot Chocolate<\/a> Library f\u00fcr <a href=\"https:\/\/graphql.org\/\">GraphQL<\/a> API und <a href=\"https:\/\/reactjs.org\/\">React<\/a> als UI-Framework gew\u00e4hlt. Und wir haben das <a href=\"https:\/\/github.com\/Centigrade\/vedaversum\">Ger\u00fcst f\u00fcr die Backend-Anwendung<\/a> erstellt. Jetzt ist es an der Zeit, unserem Backend Leben einzuhauchen.<\/p>\r\n<p>Heute werden wir dem Backend eine Authentifizierung hinzuf\u00fcgen. Wir werden die Datenpersistenz f\u00fcr unsere Anwendung definieren und implementieren, \u00fcber <em>GraphQL-Resolver <\/em>und <em>Datenlader <\/em>sprechen und ein paar Worte \u00fcber das Testen verlieren.<\/p>\r\n<p>Let&#8217;s go! \ud83d\ude09<!--more--><\/p>\r\n<h2>1.\u00a0\u00a0 Authentifizierung und Autorisierung<\/h2>\r\n<p>Im vorigen Artikel haben wir bereits besprochen, dass jeder Nutzer die Anwendung \u00f6ffnen, einige Wissenskarten erstellen, Karten lesen und andere Nutzer sehen kann, die gerade online sind. Nat\u00fcrlich soll unsere <strong>Veda Versum <\/strong>Anwendung jeden Nutzer pers\u00f6nlich kennen ?Wie Sie bereits wissen, ist die \u00fcbliche Art, sich in der IT-Welt &#8222;zu zeigen&#8220;, sich <strong>in das <\/strong>System <strong>einzuloggen<\/strong>. Ja, um dies zu tun, sollten Sie ein Konto mit Login und Passwort haben. Das ist nichts Neues. Wir m\u00fcssen die gleiche Funktionalit\u00e4t f\u00fcr unsere brandneue Anwendung bereitstellen. Aber wir werden das Rad nicht neu erfinden und das Verfahren zur Speicherung der Benutzerdaten nicht selbst einf\u00fchren. Wir werden das Standardprotokoll <a href=\"https:\/\/oauth.net\/2\/\">OAuth 2<\/a> verwenden, bei dem davon ausgegangen wird, dass wir bereits vorhandene Authentifizierungsserver f\u00fcr unsere Benutzer verwenden. Viele Anbieter wie Microsoft, Google, Twitter, Facebook und andere unterst\u00fctzen dieses Protokoll. Wenn Sie also Ihre eigene Benutzerverwaltung implementieren m\u00f6chten, aber keinen eigenen Authentifizierungsserver betreiben wollen, k\u00f6nnen Sie diese verwenden. Ein wichtiger Akteur in diesem Bereich ist <a href=\"https:\/\/auth0.com\/\">Auth0<\/a>, der sich auf Authentifizierungsdienste spezialisiert hat. Aber da unser Team GitLab f\u00fcr die meisten unserer Projekte verwendet, haben wir alle ein Konto bei GitLab. Daher wird GitLab.com unser OAuth-Anbieter sein. Der <strong>Veda <\/strong>Versum-Benutzer muss also kein weiteres Konto erstellen, um sich in <strong>Veda Versum <\/strong>anzumelden, sondern kann ein bereits bestehendes Konto in GitLab verwenden. Unsere Anwendung leitet den Benutzer auf die Anmeldeseite von GitLab um. Nachdem der Benutzer seinen Benutzernamen und sein Kennwort eingegeben hat, wird er mit einem Authentifizierungs-Token zu unserer Anwendung zur\u00fcckgeleitet. Und unsere Anwendung funktioniert nur mit diesem Token.<\/p>\r\n<p>Soweit unsere Anwendung Frontend, Backend und GitLab als OAuth 2-Provider hat, wird das Authentifizierungsschema wie folgt aussehen:<\/p>\r\n<div id=\"attachment_14873\" style=\"width: 708px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-14873\" class=\"size-full wp-image-14873\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild1-1.png\" alt=\"Authentication scheme\" width=\"698\" height=\"546\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild1-1.png 698w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild1-1-300x235.png 300w\" sizes=\"auto, (max-width: 698px) 100vw, 698px\" \/><p id=\"caption-attachment-14873\" class=\"wp-caption-text\">Abb. 1: Authentifizierungsschema<\/p><\/div>\r\n<p>Das ganze Schema sieht ein bisschen kompliziert aus. Ich werde versuchen, alle Details zu erkl\u00e4ren und Sie Schritt f\u00fcr Schritt durch alle Prozesse zu f\u00fchren.<\/p>\r\n<h3>1.1 Mutation der GitLab-Authentifizierung<\/h3>\r\n<p>Zuallererst m\u00fcssen wir OAuth auf der Einstellungsseite von GitLab einrichten. <a href=\"https:\/\/docs.gitlab.com\/ee\/integration\/oauth_provider.html#group-owned-applications\">Folgt dieser Anleitung, um eine gruppeneigene Anwendung zu erstellen.<\/a> Als Ergebnis erhalten wir eine <strong><em>Anwendungs-ID <\/em><\/strong>und ein <strong><em>Geheimnis<\/em><\/strong>. Wir ben\u00f6tigen sie f\u00fcr die n\u00e4chsten Schritte.<\/p>\r\n<p><a href=\"https:\/\/docs.gitlab.com\/ee\/api\/oauth2.html\">Dies ist der Link zur Gitlab OAuth API<\/a> zur Implementierung der Authentifizierung. Um die Schritte 2 und 4 aus <strong>Abbildung 1 <\/strong>zu implementieren, m\u00fcssen wir die GitLab-API-Methode <strong><u>oauth\/authorize <\/u><\/strong>aufrufen. Diese Methode wird von der Benutzeroberfl\u00e4che aus aufgerufen. Sp\u00e4ter m\u00fcssen wir f\u00fcr die Schritte 6 und 7 die Methode <strong><u>oauth\/token aufrufen<\/u><\/strong>. Und f\u00fcr die Schritte 8 und 9 &#8211; die Methode <strong><u>\/api\/v4\/user aufrufen<\/u><\/strong>.<\/p>\r\n<p><strong>Das Frontend ist nicht unser heutiges Ziel, hier werden wir uns auf den Backend-Teil konzentrieren<\/strong>. Um die Schritte 5 bis 10 aus <strong>Abbildung 1 <\/strong>zu implementieren, m\u00fcssen wir eine brandneue Mutation in unserer GraphQL-API erstellen, die GiltLabUser-Informationen abrufen soll. Sie nimmt den <u>GitLab Single-Use Auth Code <\/u>als Parameter und gibt ein gesichertes JWT-Token zur\u00fcck, das die Benutzerinformationen enth\u00e4lt. Diese Mutation wird die Implementierung der Schritte 6 &#8211; 9 aus <strong>Abbildung 1 <\/strong>beinhalten.<\/p>\r\n<p>Um das zu erreichen, m\u00fcssen wir unser Projekt so \u00e4ndern:<\/p>\r\n<ul>\r\n<li>Definieren Sie Parameter4 in <em>json; <\/em><\/li>\r\n<li>Erstellen Sie die Klasse <em>GitLabOauthSettings<\/em>, die die Werte dieser Parameter4 lesen und speichern wird;<\/li>\r\n<li>Definieren Sie die Schnittstelle <em>IGitlabOauthService <\/em>mit 2 Methoden darin;<\/li>\r\n<li>Definieren Sie die Klasse <em>GitLabOauthService<\/em>, die die Schnittstelle implementiert und die <em>Oauth-App-Einstellungen <\/em>und den <em>GitLab-Http-Client <\/em>als Dependency-Injection-Objekte \u00fcbernimmt;<\/li>\r\n<li>Definieren Sie eine neue Mutation mit der Methode <em>GitLabAuthenticate<\/em>. Diese Mutation wird <em>IGitlabOauthService <\/em>als Dependency Injection Objekt verwenden;<\/li>\r\n<li>Konfiguration, http-Client, GitLabOauthService und Mutation in <em>cs <\/em>einrichten.<\/li>\r\n<\/ul>\r\n<p>Die Codebasis sollte wie folgt aussehen:<\/p>\r\n<div id=\"attachment_14884\" style=\"width: 442px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-14884\" class=\"size-full wp-image-14884\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild2-1.png\" alt=\"GitLab authentication mutation class diagram\" width=\"432\" height=\"301\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild2-1.png 432w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild2-1-300x209.png 300w\" sizes=\"auto, (max-width: 432px) 100vw, 432px\" \/><p id=\"caption-attachment-14884\" class=\"wp-caption-text\">Abb. 2: GitLab authentication mutation class diagram<\/p><\/div>\r\n<p>Also, wie Linus Torvalds sagte: \u201cTalk is cheap, show me the code\u201d.<\/p>\r\n<p>&nbsp;<\/p>\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\nappsettings.json:\r\n  &quot;GitLabOauth&quot;: {\r\n    &quot;BaseAddress&quot;: &quot;https:\/\/gitlab.example.com&quot;,\r\n    &quot;ClientId&quot;: &quot;FakeClientId&quot;,\r\n    &quot;Secret&quot;: &quot;FakeSecret&quot;,\r\n    &quot;JwtSecret&quot;: &quot;FakeJwtSecret&quot;\r\n  },\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Ein Tipp zu den App-Einstellungen. Ich verwende den <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/security\/app-secrets?view=aspnetcore-5.0&amp;tabs=windows\">app <\/a>secrets-Mechanismus, um sensible Daten in der Entwicklungsmaschine statt in der appsettings.json zu speichern. Auf diese Weise kann ich den Code mit wirklich sensiblen Daten immer debuggen und sie gleichzeitig nie an das \u00f6ffentliche Repository \u00fcbergeben.<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nOAuthMutation.cs:\r\n        \/\/\/ &lt;summary&gt;\r\n        \/\/\/ This method accepts GitLab oauth code, and returns JWT token with GutLab user as claim\r\n        \/\/\/ &lt;\/summary&gt;\r\n        \/\/\/ &lt;param name=&quot;oauthCode&quot;&gt;OAuth code can be generated by this URL https:\/\/gitlab.example.com\/oauth\/authorize&lt;\/param&gt;\r\n        \/\/\/ &lt;remarks&gt;\r\n        \/\/\/ More info about GitLab OAuth https:\/\/docs.gitlab.com\/ee\/api\/oauth2.html\r\n        \/\/\/ &lt;\/remarks&gt;\r\n        public async Task&lt;string&gt; GitLabAuthenticate(string oauthCode)\r\n        {\r\n            try\r\n            {\r\n                var user = await _oauthService.GetUser(oauthCode);\r\n                if (user == null)\r\n                {\r\n                    throw new ApplicationException($&quot;Can not find GitLab user by code {oauthCode}&quot;);\r\n                }\r\n                return _oauthService.GenerateToken(user);\r\n            }\r\n            catch (Exception e)\r\n            {\r\n                _logger.LogError(e,&quot;Authentication fails&quot;);\r\n                throw;\r\n            }\r\n        }\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Die gesamte Mutationsklasse kann man im <a href=\"https:\/\/github.com\/Centigrade\/vedaversum\/blob\/main\/VedaVersum.Backend\/Api\/OAuthMutation.cs\">Repository<\/a> finden. Hier ist die Mutationsmethode, die nur zwei Servicemethoden aufruft &#8211; <strong>get user <\/strong>und <strong>generate JWT token<\/strong><\/p>\r\n\r\n\r\n\r\n<p><a href=\"https:\/\/github.com\/Centigrade\/vedaversum\/blob\/main\/VedaVersum.Backend\/OAuth\/GitLabOauthService.cs\">GitLabOauthService.cs<\/a><\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n        \/\/\/ &lt;inheritdoc \/&gt;\r\n        public string GenerateToken(User user)\r\n        {\r\n            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.JwtSecret));\r\n            var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);\r\n            var serializedUser = JsonSerializer.Serialize(user);\r\n\r\n            \/\/ generate jwt token\r\n            var claims = new&#x5B;]\r\n            {\r\n                new Claim(ClaimTypes.Name, user.Name?? &quot;unknown&quot;),\r\n                new Claim(ClaimTypes.Email, user.Email?? &quot;unknown&quot;),\r\n                new Claim(ClaimTypes.UserData, serializedUser)\r\n            };\r\n\r\n            var token = new JwtSecurityToken(\r\n                issuer: JwtIssuer,\r\n                audience: JwtIssuer,\r\n                claims: claims,\r\n                expires: DateTime.Now.AddDays(30), \/\/ Token is valid for 1 month. Don&#039;t use this parameter in production!\r\n                signingCredentials: credentials);\r\n\r\n            return new JwtSecurityTokenHandler().WriteToken(token);\r\n        }\r\n\r\n        \/\/\/ &lt;inheritdoc \/&gt;\r\n        public async Task&lt;User?&gt; GetUser(string oAuthCode)\r\n        {\r\n            var token = await GetAccessToken(oAuthCode);\r\n            return await GetGitLabUser(token);\r\n        }\r\n\r\n        private async Task&lt;string&gt; GetAccessToken(string oAuthCode)\r\n        {\r\n            var parameters = new Dictionary&lt;string, string&gt;\r\n            {\r\n                { &quot;client_id&quot;, _settings.ClientId }, \r\n                { &quot;client_secret&quot;, _settings.Secret },\r\n                { &quot;code&quot;, oAuthCode },\r\n                { &quot;grant_type&quot;, &quot;authorization_code&quot; },\r\n                { &quot;redirect_uri&quot;, &quot;https:\/\/localhost:5001&quot; }\r\n            };\r\n            var encodedContent = new FormUrlEncodedContent(parameters!);\r\n\r\n            var client = _httpClientFactory.CreateClient(GitLabHttpClientName);\r\n\r\n            var url = $&quot;{_settings.BaseAddress}\/oauth\/token&quot;;\r\n\r\n            var response = await client.PostAsync(url, encodedContent).ConfigureAwait(false);\r\n            response.EnsureSuccessStatusCode();\r\n\r\n            await using var responseStream = await response.Content.ReadAsStreamAsync();\r\n            var authTokenResponse = await JsonSerializer.DeserializeAsync&lt;OAuthTokenResponse?&gt;(responseStream);\r\n\r\n            if(string.IsNullOrEmpty(authTokenResponse?.AccessToken))\r\n            {\r\n                throw new ApplicationException($&quot;Can not get access_token from url &#039;{_settings.BaseAddress}&#039; and Auth code &#039;{oAuthCode}&#039;&quot;);\r\n            }\r\n            return authTokenResponse.AccessToken;\r\n        }\r\n\r\n        private async Task&lt;User?&gt; GetGitLabUser(string token)\r\n        {\r\n            string url = $&quot;{_settings.BaseAddress}\/api\/v4\/user&quot;;\r\n\r\n            var client = _httpClientFactory.CreateClient(GitLabHttpClientName);\r\n            client.DefaultRequestHeaders.Authorization\r\n                = new AuthenticationHeaderValue(&quot;Bearer&quot;, token);\r\n            client.DefaultRequestHeaders.Add(&quot;User-Agent&quot;, &quot;VedaVersum&quot;);\r\n\r\n            var response = await client.GetAsync(url);\r\n\r\n            response.EnsureSuccessStatusCode();\r\n            \r\n            await using var responseStream = await response.Content.ReadAsStreamAsync();\r\n            return await JsonSerializer.DeserializeAsync&lt;User?&gt;(responseStream);\r\n        }\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Die gesamte Serviceklasse findet ihr im <a href=\"https:\/\/github.com\/Centigrade\/vedaversum\/tree\/main\/VedaVersum.Backend\">Repository<\/a>. Hier sind drei Hauptmethoden:<\/p>\r\n<p>Die erste Methode ruft die GitLab-API auf, um ein Zugriffstoken zu erhalten. Dieses Token ben\u00f6tigen wir in der zweiten Methode, um den GitLab-Benutzer von der GitLab-API abzurufen. Und die dritte Methode generiert ein JWT-Token mit Benutzerinformationen, die durch den SHA-Algorithmus mit einem geheimen Schl\u00fcssel gesichert sind, der in den Anwendungseinstellungen gespeichert ist.<\/p>\r\n\r\n\r\n\r\n<p><a href=\"https:\/\/github.com\/Centigrade\/vedaversum\/blob\/main\/VedaVersum.Backend\/Startup.cs\">startup.cs<\/a>:<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n        public void ConfigureServices(IServiceCollection services)\r\n        {\r\n            \/\/ GitLab authorization configuration\r\n            var gitLabOauthConfig = new GitLabOauthSettings();\r\n            Configuration.GetSection(&quot;GitLabOauth&quot;).Bind(gitLabOauthConfig);\r\n            services.AddSingleton(gitLabOauthConfig);\r\n\r\n            \/\/ GitLab authorization\r\n            services.AddHttpClient(GitLabOauthService.GitLabHttpClientName);\r\n            services.AddTransient&lt;IGitLabOauthService, GitLabOauthService&gt;();\r\n            services\r\n                .AddGraphQLServer()\r\n                .AddInMemorySubscriptions()\r\n                .AddQueryType&lt;VedaVersumQuery&gt;()\r\n                    .AddType&lt;VedaVersumCardObjectType&gt;()\r\n                .AddMutationType(d =&gt; d.Name(&quot;Mutation&quot;))\r\n                    .AddType&lt;OAuthMutation&gt;() \/\/ Register new OAuth Mutation in the GraphQL API\r\n                    .AddType&lt;VedaVersumMutation&gt;()\r\n                .AddSubscriptionType&lt;VedaVersumSubscription&gt;();\r\n       }\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Hier lesen wir die Konfiguration aus den App-Einstellungen und f\u00fcgen die Einstellungen zum DI-Container hinzu. Wir pushen einen neuen <em>GitLabOauthService <\/em>in den DI-Container und registrieren eine neue <em>OAuthMutation <\/em>in der GraphQL-API<\/p>\r\n<p>Jetzt ist unser Dienst bereit, JWT-Tokens zu erzeugen. In <strong>Abbildung 1 sehen <\/strong>Sie, dass wir noch irgendwo den GitLab-Authentifizierungscode f\u00fcr den einmaligen Gebrauch abrufen m\u00fcssen. Dies sind die Schritte 2, 3 und 4, die in Zukunft von Frontend verwaltet werden. Da wir aber noch kein Frontend haben, werden wir diese Schritte manuell durchf\u00fchren. Wir sollten den Browser \u00f6ffnen und diese Url an GitLab senden:<\/p>\r\n\r\n\r\n\r\n<p><em>https:\/\/gitlab.<span style=\"color: #ff0000;\">example<\/span>.com\/oauth\/authorize?client_id=<span style=\"color: #ff0000;\">APP_ID<\/span>&amp;redirect_uri=https:\/\/localhost:5001&amp;response_type=code&amp;state=<span style=\"color: #ff0000;\">TEMPORARY_STATE<\/span>&amp;scope=read_user<\/em><\/p>\r\n\r\n\r\n\r\n<p>Die 3 Parameter sollte man ersetzen:<\/p>\r\n<ul>\r\n<li>example.com f\u00fcr die URL Ihres Gitlab-Servers<\/li>\r\n<li>APP_ID auf Ihre eigene Anwendungs-ID, die Sie beim Erstellen der <a href=\"https:\/\/docs.gitlab.com\/ee\/integration\/oauth_provider.html#group-owned-applications\">gruppeneigenen GitLab-Anwendung<\/a> erhalten haben<\/li>\r\n<li>TEMPORARY_STATE kann eine beliebige Zeichenfolge sein. Ich verwende einen GUID-Generator, um ihn zu erzeugen.<\/li>\r\n<\/ul>\r\n<p>Wenn Sie zu dieser URL navigieren, werden Sie zur GitLab-Authentifizierungsseite weitergeleitet. Und nach erfolgreicher Anmeldung werden Sie zu https:\/\/localhost:5001 weitergeleitet, das noch nicht existiert. Alles, was Sie im Moment brauchen, steht in der Adresszeile des Browsers:<\/p>\r\n\r\n\r\n\r\n\r\n\r\n<p><em>https:\/\/localhost:5001\/?code=<span style=\"color: #ff0000;\">4debdf62d4458dbd02be661661506c869582232b0bf7632d5e10e74045318eb0<\/span>&amp;state=29aa0c42-58d0-4129-9d76-33094fa56401<\/em><\/p>\r\n\r\n\r\n\r\n<p>Wir werden diesen Code in unserer Authentifizierungsmutation verwenden. Wenn Sie die VedaVersum-Anwendung starten, wird die GraphQL <a href=\"https:\/\/chillicream.com\/docs\/bananacakepop\">BananaCakePop<\/a> IDE angezeigt, in der Sie die Mutation aufrufen k\u00f6nnen:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"698\" height=\"265\" class=\"wp-image-14882\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild3-1.png\" alt=\"Authentication mutation execution\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild3-1.png 698w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild3-1-300x114.png 300w\" sizes=\"auto, (max-width: 698px) 100vw, 698px\" \/>\r\n<figcaption>Abb. 3: Ausf\u00fchrung der Authentifizierungsmutation<\/figcaption>\r\n<\/figure>\r\n\r\n\r\n\r\n<p>Wenn Sie alles richtig gemacht haben, wird die Mutation das JWT-Token zur\u00fcckgeben. Sie k\u00f6nnen dieses Token mit <a href=\"https:\/\/jwt.io\/\">https:\/\/jwt.io\/<\/a> testen.<\/p>\r\n\r\n\r\n\r\n<p>&nbsp;<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"522\" height=\"378\" class=\"wp-image-14871\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild4-1.png\" alt=\"JWT token\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild4-1.png 522w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild4-1-300x217.png 300w\" sizes=\"auto, (max-width: 522px) 100vw, 522px\" \/>\r\n<figcaption>Abb 4. JWT token<\/figcaption>\r\n<\/figure>\r\n\r\n\r\n\r\n<p>Das bedeutet, dass wir das richtige Token haben. In Zukunft wird dieses Token irgendwo im Frontend gespeichert werden. Und jedes Mal, wenn das Frontend Abfragen oder Mutationen in unserer GraphQL-API aufruft, sollte es dieses Token als http-Header zu jeder Anfrage hinzuf\u00fcgen, um den Benutzer zu authentifizieren. Da wir aber noch kein Frontend haben, sollten wir dieses Token irgendwo im Notizblock aufbewahren. Wir werden es im n\u00e4chsten Kapitel brauchen.<\/p>\r\n\r\n\r\n\r\n<h3 class=\"wp-block-heading\">1.2 Autorisierung durch JWT-Token<\/h3>\r\n\r\n\r\n\r\n<p>Wir haben bereits einen Mechanismus zur Authentifizierung von Benutzern und zur Speicherung von Benutzerinformationen als verschl\u00fcsselte JWT-Token am Frontend geschaffen. So weit, so gut. Jetzt ist es an der Zeit, unsere GraphQL-API zu sichern und nur den Nutzern mit g\u00fcltigen JWT-Tokens die Nutzung ihrer Abfragen und Mutationen zu erlauben.<\/p>\r\n<p>Jede HTTP-Anfrage in Asp.net Core Server durchl\u00e4uft eine &#8222;Pipeline&#8220;. Diese Pipeline hat ihren eigenen Kontext. Und Entwickler k\u00f6nnen verschiedene &#8222;Middleware&#8220;-Typen in diese Pipeline einf\u00fcgen und den Kontext manipulieren. Asp.net Core verf\u00fcgt \u00fcber <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/security\/?view=aspnetcore-6.0\">Standard-Autorisierungsmechanismen<\/a>, die ebenfalls als Middleware implementiert sind. Wir haben auch den GraphQL-Server von <a href=\"https:\/\/chillicream.com\/docs\/hotchocolate\">ChilliCream<\/a> als Middleware hinzugef\u00fcgt, der f\u00fcr jede Anfrage einen eigenen Kontext hat.<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"698\" height=\"67\" class=\"wp-image-14867\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild5.png\" alt=\"Asp.net core pipeline\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild5.png 698w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild5-300x29.png 300w\" sizes=\"auto, (max-width: 698px) 100vw, 698px\" \/>\r\n<figcaption>Abb. 5. Asp.net core pipeline<\/figcaption>\r\n<\/figure>\r\n\r\n\r\n\r\n<p>Erstens m\u00fcssen wir unsere JWT-Autorisierungslogik in die Standard-Authentifizierungs-Middleware integrieren. Zweitens m\u00fcssen wir User zum GraphQL-Middleware-Kontext hinzuf\u00fcgen.<\/p>\r\n<p>Um die JWT-Token-Authentifizierung als Standard-Asp.net-Core-Middleware hinzuzuf\u00fcgen, m\u00fcssen wir die Nuget-Pakete &#8222;Microsoft.AspNetCore.Authentication.JwtBearer&#8220; und &#8222;System.IdentityModel.Tokens.Jwt&#8220; zu unserem Projekt hinzuf\u00fcgen. Und dann richten Sie den Authentifizierungsdienst im DI-Container wie folgt ein:<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n            \/\/ Token validation\r\n            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\r\n                .AddJwtBearer(options =&gt;\r\n                {\r\n                    options.TokenValidationParameters = new TokenValidationParameters\r\n                    {\r\n                        ValidateIssuer = true,\r\n                        ValidateAudience = true,\r\n                        ValidateLifetime = true,\r\n                        ValidateIssuerSigningKey = true,\r\n                        ValidIssuer = GitLabOauthService.JwtIssuer,\r\n                        ValidAudience = GitLabOauthService.JwtIssuer,\r\n                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(gitLabOauthConfig.JwtSecret))\r\n                    };\r\n                });\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>F\u00fcgen Sie die Middleware f\u00fcr Authentifizierung und Autorisierung in der Methode <em>Configure <\/em>hinzu:<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n            app\r\n                .UseWebSockets()\r\n                .UseRouting()\r\n                .UseAuthentication() \/\/ Authentication middleware\r\n                .UseAuthorization() \/\/ Authorization middleware\r\n                .UseEndpoints(endpoints =&gt;\r\n                {\r\n                    endpoints.MapGraphQL();\r\n                });\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Nun, wir haben gerade die Bl\u00f6cke 1 und 2 aus <strong>Abbildung 5 <\/strong>implementiert.<\/p>\r\n<p>Um die Autorisierung zur GraphQL Server Middleware hinzuzuf\u00fcgen, m\u00fcssen wir das neue Nuget-Paket &#8222;HotChocolate.AspNetCore.Authorization&#8220; hinzuf\u00fcgen. Und um die Autorisierung zum GraphQL DI Setup in startup.cs hinzuzuf\u00fcgen:<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n        services\r\n            .AddGraphQLServer()\r\n\t\u2026\r\n            .AddAuthorization()\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Danach m\u00fcssen wir das Attribut [Authorize] zu den Klassen <em>VedaVersumQuery<\/em>, <em>VedaVersumMutation <\/em>und <em>VedaVersumSubscription <\/em>hinzuf\u00fcgen:<\/p>\r\n\r\n\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"409\" height=\"357\" class=\"wp-image-14880\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild6.png\" alt=\"Add [Authorize] attribute to the VedaVersumQuery\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild6.png 409w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild6-300x262.png 300w\" sizes=\"auto, (max-width: 409px) 100vw, 409px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>Hier haben wir Block Nummer 3 aus <strong>Abbildung 5 <\/strong>implementiert.<\/p>\r\n<p>Jetzt k\u00f6nnen wir versuchen, eine beliebige Abfrage oder Mutation ohne Autorisierungstoken aufzurufen:<\/p>\r\n\r\n\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"546\" height=\"308\" class=\"wp-image-14878\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild7.png\" alt=\"Authentication error for anonymous users\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild7.png 546w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild7-300x169.png 300w\" sizes=\"auto, (max-width: 546px) 100vw, 546px\" \/>\r\n<figcaption>Abb. 6: Authentifizierungsfehler f\u00fcr anonyme Benutzer<\/figcaption>\r\n<\/figure>\r\n\r\n\r\n\r\n<p>Wie Sie sehen k\u00f6nnen, k\u00f6nnen anonyme Benutzer unsere GraphQL API nicht mehr nutzen.<\/p>\r\n<p>F\u00fcgen wir jedoch den http-Header {&#8222;Authorization&#8220;: &#8222;Bearer &#8230;&#8220;} mit einem g\u00fcltigen Token zu unserer Anfrage hinzuf\u00fcgen, wird die Mutation wie erwartet ausgef\u00fchrt:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"551\" height=\"296\" class=\"wp-image-14876\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild8-1.png\" alt=\"Normal mutation execution for authorized users\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild8-1.png 551w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild8-1-300x161.png 300w\" sizes=\"auto, (max-width: 551px) 100vw, 551px\" \/>\r\n<figcaption>Abb. 7: Normale Mutationsausf\u00fchrung f\u00fcr autorisierte Benutzer<\/figcaption>\r\n<\/figure>\r\n\r\n\r\n\r\n<p>Zu guter Letzt: Wenn der Benutzer autorisiert ist, wird das User-Objekt vom Typ <strong><em>PrincipalIdentity <\/em><\/strong>zum asp.net Pipeline-Kontext hinzugef\u00fcgt. Und dieses Objekt wird in allen Middlewares nach der &#8222;Autorisierungs&#8220;-Middleware zug\u00e4nglich sein, einschlie\u00dflich des GraphQL Resolvers im Block (<strong>Abbildung 5<\/strong>). Aber in der Gesch\u00e4ftslogik m\u00fcssen wir das GitLabUser-Objekt mit allen angeforderten Eigenschaften bedienen. Wir k\u00f6nnen diese <strong><em>PrincipalIdentity <\/em><\/strong>direkt im Resolver in <strong><em>GitLabUser <\/em><\/strong>umwandeln. Aber wenn wir viele Resolver haben, m\u00fcssen wir diese Umwandlung jedes Mal vornehmen. Besser w\u00e4re es, <strong><em>PrincipalIdentity zu <\/em><\/strong>einem fr\u00fcheren Zeitpunkt in der Pipeline in <strong><em>GitLabUser <\/em><\/strong>umzuwandeln und das umgewandelte GitLabUser-Objekt in den Kontext der Pipeline zu stellen. Hier ein Beispiel, wie dies auf der Ebene von Block 3 (aus <strong>Abbildung 5<\/strong>) geschehen kann:<\/p>\r\n\r\n\r\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n        services\r\n            .AddGraphQLServer()\r\n\t\u2026\r\n            .AddAuthorization()\r\n            .AddHttpRequestInterceptor(\r\n                (context, executor, builder, ct) =&gt;\r\n                {\r\n                    \/\/ Deserializing GitLab user from JWT token data\r\n                    if(context.User != null)\r\n                    {\r\n                        var serializedUser = context.User.Claims.Where(c =&gt; c.Type == ClaimTypes.UserData)\r\n                            .Select(c =&gt; c.Value).SingleOrDefault();\r\n                        if(!string.IsNullOrEmpty(serializedUser))\r\n                        {\r\n                            var user = JsonSerializer.Deserialize&lt;User&gt;(serializedUser);\r\n                            builder.SetProperty(&quot;GitLabUser&quot;, user);\r\n                        }\r\n                    }\r\n                    return ValueTask.CompletedTask;\r\n                })\r\n            .ModifyRequestOptions(opt =&gt; opt.IncludeExceptionDetails = true);\r\n\r\n<\/pre><\/div>\r\n\r\n\r\n<p>Dieser Interceptor holt sich <strong><em>GitlabUser <\/em><\/strong>von <strong><em>PrincipalIdentity <\/em><\/strong>und setzt dieses Objekt in den Kontext mit dem Schl\u00fcssel &#8222;<u>GitLabUser<\/u>&#8222;. Sp\u00e4ter k\u00f6nnen wir dieses Objekt in jedem unserer Resolver wie folgt verwenden:<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1014\" height=\"696\" class=\"wp-image-14869\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild9.png\" alt=\"interceptor gets GitlabUser from PrincipalIdentity \" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild9.png 1014w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild9-300x206.png 300w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Bild9-768x527.png 768w\" sizes=\"auto, (max-width: 1014px) 100vw, 1014px\" \/><\/figure>\r\n\r\n\r\n\r\n<p>OK, wir haben die vollst\u00e4ndige Authentifizierung und Autorisierung zu unserem Backend hinzugef\u00fcgt, indem wir GitLab als OAuth 2-Authentifizierungsanbieter verwenden. Au\u00dferdem haben wir unsere GraphQL-API vollst\u00e4ndig abgesichert.<\/p>\r\n<p>Und nicht zuletzt haben wir einen der kompliziertesten Teile des Programms erstellt und die einzige M\u00f6glichkeit, die Leistungsf\u00e4higkeit zu testen, die Anwendung zu starten und alle notwendigen Schritte manuell auszuf\u00fchren. Aber manchmal m\u00fcssen wir schnell \u00fcberpr\u00fcfen, ob alles funktioniert. Und noch besser ist es, nach jedem Commit zu testen, ob wir nichts kaputt gemacht haben. Ich habe ein Testprojekt erstellt, das das NUnit-Testframework und die Moq-Bibliothek zur Erstellung von Mock-Objekten verwendet. Es gibt ein Testszenario, in dem ich die .Net-HTTP-Client-Klasse mocke und pr\u00fcfe, ob die Methoden der Gitlab-Web-API mit den entsprechenden URIs, Parametern und Autorisierungs-Tokens aufgerufen werden. Sie k\u00f6nnen sich den Code im <a href=\"https:\/\/github.com\/Centigrade\/vedaversum\/blob\/main\/VedaVersum.Test\/OAuthServiceTest.cs\">GitHub-Repository<\/a> ansehen. Wir werden in unserem n\u00e4chsten Artikel ausf\u00fchrlich \u00fcber Unit-Tests sprechen.<\/p>\r\n<p>Das war&#8217;s f\u00fcr heute! \ud83d\ude42 Im n\u00e4chsten Artikel werden wir uns mit der Datenpersistenz und GraphQL-Resolvern und Datenladern besch\u00e4ftigen. Bis Bald! \ud83d\ude42<\/p>\r\n<p>&nbsp;<\/p>\r\n","protected":false},"author":66,"featured_media":0,"template":"","tags":[887,888,236,890,514,112,617],"class_list":["post-14902","blog","type-blog","status-publish","hentry","tag-net-de","tag-api-de","tag-csharp","tag-graphql-de","tag-react","tag-software-entwickler","tag-ux-engineering"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog\/14902","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog"}],"about":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/types\/blog"}],"author":[{"embeddable":true,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/users\/66"}],"version-history":[{"count":7,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog\/14902\/revisions"}],"predecessor-version":[{"id":14914,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog\/14902\/revisions\/14914"}],"wp:attachment":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/media?parent=14902"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/tags?post=14902"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}