﻿
{"id":19472,"date":"2020-11-29T20:53:20","date_gmt":"2020-11-29T20:53:20","guid":{"rendered":"https:\/\/www.gencayyildiz.com\/blog\/?p=19472"},"modified":"2020-11-29T20:53:20","modified_gmt":"2020-11-29T20:53:20","slug":"identityserver4-yazi-serisi-21-angular-ile-identityserver4-kullanimi","status":"publish","type":"post","link":"https:\/\/www.gencayyildiz.com\/blog\/identityserver4-yazi-serisi-21-angular-ile-identityserver4-kullanimi\/","title":{"rendered":"IdentityServer4 Yaz\u0131 Serisi #21 &#8211; Angular \u0130le IdentityServer4 Kullan\u0131m\u0131 ve Refresh Token(Silent Refresh)"},"content":{"rendered":"<div id=\"fb-root\"><\/div>\n<p>Merhaba,<\/p>\n<p><a href=\"https:\/\/www.gencayyildiz.com\/blog\/identityserver4-yazi-serisi\/\" rel=\"noopener noreferrer\" target=\"_blank\">IdentityServer4 Yaz\u0131 Serisi<\/a>nin bu yirmibirinci makalesinde art\u0131k IdentityServer4 framework&#8217;\u00fc ile bir SPA(Single Page Application) uygulamas\u0131n\u0131n nas\u0131l kullan\u0131ld\u0131\u011f\u0131na s\u0131ra geldi\u011fini d\u00fc\u015f\u00fcnerek Angular ile \u00f6rneklendirmeye karar verdim. SPA teknolojilerinden Angular mimarisinde ele alaca\u011f\u0131m\u0131z bu i\u00e7eri\u011fimizde kullanaca\u011f\u0131m\u0131z k\u00fct\u00fcphane ve yakla\u015f\u0131mlar\u0131 React, Vue.js vs. gibi UI teknolojilerinde de birebir kullanabilece\u011finizi ve modelleyebilece\u011finizi ifade ederek esas\u0131nda teknik a\u00e7\u0131dan Angular kullansakta, teoride herhangi bir UI teknolojisine ba\u011f\u0131ml\u0131 bir makale olmayaca\u011f\u0131n\u0131 bildirdikten sonra hi\u00e7 vakit kaybetmeden konuya giri\u015f yapal\u0131m.<\/p>\n<h3 style=\"color:#e83e8c;\">Ba\u015flarken<\/h3>\n<p>Angular ile IdentityServer4 kullan\u0131m\u0131n\u0131 inceleyebilmek i\u00e7in ilk olarak bir Angular uygulamas\u0131 olu\u015ftural\u0131m ve i\u00e7erisine <a href=\"https:\/\/www.npmjs.com\/package\/oidc-client\" rel=\"noopener noreferrer\" target=\"_blank\">oidc-client<\/a> paketini y\u00fckleyelim.<\/p>\n<p><span style=\"font-size:11px;\"><strong>oidc-client paketi<\/strong>;  UI teknolojilerinde OAuth 2.0 ve OpenId Connect protokollerini destekleyen bir pakettir.<\/span><\/p>\n<p>Ard\u0131ndan ilgili uygulaman\u0131n &#8216;angular.json&#8217; isimli dosyas\u0131nda a\u015fa\u011f\u0131daki konfig\u00fcrasyonu ger\u00e7ekle\u015ftirelim.<br \/>\n<a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi-297x300.jpg\" alt=\"IdentityServer4 Yaz\u0131 Serisi #21 - Angular \u0130le IdentityServer4 Kullan\u0131m\u0131\" width=\"297\" height=\"300\" class=\"aligncenter size-medium wp-image-19476\" srcset=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi-297x300.jpg 297w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi.jpg 574w\" sizes=\"auto, (max-width: 297px) 100vw, 297px\" \/><\/a><br \/>\nEvet&#8230; B\u00f6ylece IdentityServer4 i\u015flemleri i\u00e7in geli\u015ftirmeye haz\u0131r bir Angular uygulamas\u0131 olu\u015fturmu\u015f olduk.<\/p>\n<p>Makale s\u00fcrecinde bir di\u011fer ihtiyac\u0131m\u0131z ise IdentityServer4 framework&#8217;\u00fc ile geli\u015ftirilmi\u015f bir Auth Server uygulamas\u0131 ve yetkilendirme \u00f6rneklendirmesi i\u00e7in ise bir API tasar\u0131m\u0131d\u0131r. Sizler isterseniz bu uygulamalar\u0131 kendiniz geli\u015ftirebilir yahut <a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/SPAClientIdentityServer4Example.zip\">\u015furadaki<\/a> taraf\u0131mca haz\u0131rlanm\u0131\u015f hali haz\u0131r uygulamay\u0131 kullanabilirsiniz. <span style=\"font-size:11px;\">Not: Bu uygulaman\u0131n i\u00e7eri\u011fi, makalenin seyriyle birebir uyumludur.<\/span><\/p>\n<h3 style=\"color:#e83e8c;\">IdentityServer4(Auth Server) Tasar\u0131m\u0131<\/h3>\n<p>\u0130lk olarak Auth Server&#8217;\u0131n tasar\u0131msal altyap\u0131s\u0131ndan bahsetmeli ve bir UI teknolojisine uygun nas\u0131l olmas\u0131 gerekti\u011fi \u00fczerine hasbihalde bulunmal\u0131y\u0131z. <\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    static public class Config\r\n    {\r\n        #region Scoped\r\n        public static IEnumerable&lt;ApiScope&gt; GetApiScopes() =&gt;\r\n            new List&lt;ApiScope&gt;\r\n            {\r\n                new ApiScope(&quot;Garanti.Write&quot;,&quot;Garanti bankas\u0131 yazma izni.&quot;),\r\n                new ApiScope(&quot;Garanti.Read&quot;,&quot;Garanti bankas\u0131 okuma izni.&quot;)\r\n            };\r\n        #endregion\r\n        #region Resources\r\n        public static IEnumerable&lt;ApiResource&gt; GetApiResources() =&gt;\r\n            new List&lt;ApiResource&gt;\r\n            {\r\n                new ApiResource(&quot;Garanti&quot;){ Scopes = {&quot;Garanti.Write&quot;, &quot;Garanti.Read&quot; } }\r\n            };\r\n        #endregion\r\n        #region Clients\r\n        public static IEnumerable&lt;Client&gt; GetClients() =&gt;\r\n            new List&lt;Client&gt;\r\n            {\r\n                new Client\r\n                {\r\n                    ClientId = &quot;AngularClient&quot;,\r\n                    ClientName = &quot;Angular Client&quot;,\r\n                    RequireClientSecret = false,\r\n                    AllowedScopes = {\r\n                        &quot;Garanti.Write&quot;,\r\n                        &quot;Garanti.Read&quot;,\r\n                        IdentityServerConstants.StandardScopes.Profile,\r\n                        IdentityServerConstants.StandardScopes.OpenId,\r\n                        IdentityServerConstants.StandardScopes.Email,\r\n                        &quot;Roles&quot;\r\n                    },\r\n                    RedirectUris = {&quot;http:\/\/localhost:4200\/callback&quot;},\r\n                    AllowedCorsOrigins = {&quot;http:\/\/localhost:4200&quot;},\r\n                    PostLogoutRedirectUris = {&quot;http:\/\/localhost:4200&quot;},\r\n                    AllowedGrantTypes = GrantTypes.Code,\r\n                    RequirePkce = true,\r\n                }\r\n            };\r\n        #endregion\r\n        #region Users\r\n        public static IEnumerable&lt;TestUser&gt; GetTestUsers() =&gt;\r\n            new List&lt;TestUser&gt;\r\n            {\r\n                new TestUser\r\n                {\r\n                    SubjectId = &quot;test-user1&quot;,\r\n                    Username = &quot;test-user1&quot;,\r\n                    Password = &quot;12345&quot;,\r\n                    Claims =\r\n                    {\r\n                        new Claim(JwtRegisteredClaimNames.Email, &quot;test-user@gmail.com&quot;),\r\n                        new Claim(&quot;role&quot;, &quot;admin&quot;),\r\n                        new Claim(&quot;authority&quot;,&quot;true&quot;)\r\n                    }\r\n                }\r\n            };\r\n        #endregion\r\n        #region Identity Resources\r\n        public static IEnumerable&lt;IdentityResource&gt; GetIdentityResources() =&gt;\r\n            new List&lt;IdentityResource&gt;\r\n            {\r\n                new IdentityResources.OpenId(),\r\n                new IdentityResources.Profile(),\r\n                new IdentityResources.Email(),\r\n                new IdentityResource\r\n                {\r\n                    Name = &quot;Roles&quot;,\r\n                    DisplayName = &quot;Roles&quot;,\r\n                    Description = &quot;User roles&quot;,\r\n                    UserClaims = {&quot;role&quot;, &quot;authority&quot;}\r\n                }\r\n            };\r\n        #endregion\r\n    }\r\n<\/pre>\n<p>Yukar\u0131da makele serimiz boyunca \u015fimdiye kadar inceledi\u011fimiz t\u00fcm yap\u0131lar\u0131 bar\u0131nd\u0131ran &#8216;Config.cs&#8217; dosyas\u0131n\u0131n i\u00e7eri\u011fini g\u00f6rmekteyiz. \u0130lgili yap\u0131lanmada bu makale konusuyla alakadar noktalara \u00f6zel bir \u015fekilde temas etmemiz gerekirse e\u011fer;<\/p>\n<ul>\n<li><strong>19 &#8211; 41. sat\u0131r aral\u0131\u011f\u0131<\/strong><br \/>\nSistemde var olan client&#8217;lar\u0131n tan\u0131mland\u0131\u011f\u0131 aland\u0131r.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    .\r\n    .\r\n    .\r\n    public static IEnumerable&lt;Client&gt; GetClients() =&gt;\r\n        new List&lt;Client&gt;\r\n        {\r\n            new Client\r\n            {\r\n                ClientId = &quot;AngularClient&quot;,\r\n                ClientName = &quot;Angular Client&quot;,\r\n                RequireClientSecret = false,\r\n                AllowedScopes = {\r\n                    &quot;Garanti.Write&quot;,\r\n                    &quot;Garanti.Read&quot;,\r\n                    IdentityServerConstants.StandardScopes.Profile,\r\n                    IdentityServerConstants.StandardScopes.OpenId,\r\n                    IdentityServerConstants.StandardScopes.Email,\r\n                    &quot;Roles&quot;\r\n                },\r\n                RedirectUris = {&quot;http:\/\/localhost:4200\/callback&quot;},\r\n                AllowedCorsOrigins = {&quot;http:\/\/localhost:4200&quot;},\r\n                PostLogoutRedirectUris = {&quot;http:\/\/localhost:4200&quot;},\r\n                AllowedGrantTypes = GrantTypes.Code,\r\n                RequirePkce = true,\r\n            }\r\n        };\r\n    .\r\n    .\r\n    .\r\n<\/pre>\n<p>Tan\u0131mlanan &#8216;AngularClient&#8217; isimli client&#8217;\u0131n \u00f6zelliklerine g\u00f6z atarsak e\u011fer ilk olarak client secret de\u011ferinin olmad\u0131\u011f\u0131n\u0131 g\u00f6r\u00fcr\u00fcz. Bunun sebebi <a href=\"https:\/\/www.gencayyildiz.com\/blog\/identityserver4-yazi-serisi-20-pkceproof-key-for-code-exchange\/\" rel=\"noopener noreferrer\" target=\"_blank\">PKCE(Proof Key for Code Exchange)<\/a> ba\u015fl\u0131kl\u0131 makalede ele ald\u0131\u011f\u0131m\u0131z g\u00fcvenlik a\u00e7\u0131\u011f\u0131na sebep olabilecek bir problem durumundan kaynaklanmaktad\u0131r. Bu sebepten dolay\u0131 &#8216;RequireClientSecret&#8217; \u00f6zelli\u011fine &#8216;false&#8217; ve &#8216;RequirePkce&#8217; \u00f6zelli\u011fine ise &#8216;true&#8217; de\u011feri set edilmektedir..<\/p>\n<blockquote><p><em style=\"color:purple;\">SPA uygulamalar\u0131nda g\u00fcvenlik a\u00e7\u0131\u011f\u0131 olu\u015fmamas\u0131 i\u00e7in client secret de\u011feri kullanmamal\u0131y\u0131z!<\/em><\/p><\/blockquote>\n<p>&#8216;AllowedCorsOrigins &#8216; \u00f6zelli\u011finde ise ilgili kullan\u0131c\u0131n\u0131n profil bilgileri(Profile), subject id de\u011feri(OpenId), email gibi bilgileri ile birlikte API resource&#8217;e eri\u015fim sa\u011flayabilmesi i\u00e7in &#8216;Garanti.Write&#8217; ve &#8216;Garanti.Read&#8217; scope&#8217;lar\u0131 talep edilmektedir. Ayriyetten &#8216;Roles&#8217; isimli custom geli\u015ftirilmi\u015f identity resource&#8217;de talep edilmektedir.<\/p>\n<p>Client uygulamas\u0131 taray\u0131c\u0131da \u00e7al\u0131\u015fan UI teknolojisi olaca\u011f\u0131ndan dolay\u0131 Auth Server&#8217;a eri\u015fimin sa\u011flanabilmesi i\u00e7in <a href=\"https:\/\/www.gencayyildiz.com\/blog\/asp-net-core-uygulamalarinda-corscross-origin-resource-sharing-politikasi-ayarlama\/\" rel=\"noopener noreferrer\" target=\"_blank\">Cross-Origin-Resource-Sharing<\/a> politikas\u0131n\u0131n ayarlanmas\u0131 gerekmektedir. Bunun i\u00e7in &#8216;AllowedCorsOrigins&#8217; \u00f6zelli\u011fi kullan\u0131lmaktad\u0131r.\n<\/li>\n<li><strong>62 &#8211; 75. sat\u0131r aral\u0131\u011f\u0131<\/strong><br \/>\nClient&#8217;lar\u0131n bilgileri olan identity resource&#8217;lerin tan\u0131mland\u0131\u011f\u0131 aland\u0131r.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n        .\r\n        .\r\n        .\r\n        public static IEnumerable&lt;IdentityResource&gt; GetIdentityResources() =&gt;\r\n            new List&lt;IdentityResource&gt;\r\n            {\r\n                new IdentityResources.OpenId(),\r\n                new IdentityResources.Profile(),\r\n                new IdentityResources.Email(),\r\n                new IdentityResource\r\n                {\r\n                    Name = &quot;Roles&quot;,\r\n                    DisplayName = &quot;Roles&quot;,\r\n                    Description = &quot;User roles&quot;,\r\n                    UserClaims = {&quot;role&quot;, &quot;authority&quot;}\r\n                }\r\n            };\r\n        .\r\n        .\r\n        .\r\n<\/pre>\n<p>G\u00f6r\u00fcld\u00fc\u011f\u00fc \u00fczere \u00f6ntan\u0131ml\u0131 olan &#8216;OpenId&#8217;, &#8216;Profile&#8217; ve &#8216;Email&#8217; bilgilerinin yan\u0131nda &#8216;role&#8217; ve &#8216;authority&#8217; claim&#8217;lerini tutan &#8216;Roles&#8217; isimli custom identity resource&#8217;de tan\u0131mlanmaktad\u0131r. Zaten hat\u0131rlarsan\u0131z e\u011fer bu de\u011fer yukar\u0131daki client tan\u0131mlamas\u0131nda scope olarak \u00e7a\u011fr\u0131lmaktad\u0131r.\n<\/li>\n<li><strong>44 &#8211; 59. sat\u0131r aral\u0131\u011f\u0131<\/strong><br \/>\nTest user&#8217;lar\u0131n tan\u0131mland\u0131\u011f\u0131 aland\u0131r.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n        .\r\n        .\r\n        .\r\n        public static IEnumerable&lt;TestUser&gt; GetTestUsers() =&gt;\r\n            new List&lt;TestUser&gt;\r\n            {\r\n                new TestUser\r\n                {\r\n                    SubjectId = &quot;test-user1&quot;,\r\n                    Username = &quot;test-user1&quot;,\r\n                    Password = &quot;12345&quot;,\r\n                    Claims =\r\n                    {\r\n                        new Claim(JwtRegisteredClaimNames.Email, &quot;test-user@gmail.com&quot;),\r\n                        new Claim(&quot;role&quot;, &quot;admin&quot;),\r\n                        new Claim(&quot;authority&quot;,&quot;true&quot;)\r\n                    }\r\n                }\r\n            };\r\n        .\r\n        .\r\n        .\r\n<\/pre>\n<p>Tek dikkat etmenizi istedi\u011fim custom eklenen identity resource&#8217;un claim de\u011ferlerinin kullan\u0131c\u0131ya eklenmi\u015f olmas\u0131d\u0131r. B\u00f6ylece hem ilgili identity resource de\u011ferlerini kullan\u0131c\u0131 ta\u015f\u0131yacak hem de client taraf\u0131ndan talep edilmi\u015f olacakt\u0131r.\n<\/li>\n<\/ul>\n<p>Bu nazar-\u0131 dikkat edilen noktalar d\u0131\u015f\u0131nda Auth Server ile birlikte API&#8217;lar\u0131n &#8216;Startup.cs&#8217; konfig\u00fcrasyonu vs. gibi geri kalan i\u015flemlerde ki t\u00fcm s\u00fcre\u00e7 yaz\u0131 serimiz boyunca ele al\u0131nd\u0131\u011f\u0131 gibidir. Dolay\u0131s\u0131yla ilgili a\u015famalara temas etmeksizin bu andan itibaren client uygulamas\u0131(Angular) boyutundan makalemize devam edebiliriz&#8230;<\/p>\n<h3 style=\"color:#e83e8c;\">Angular Tasar\u0131m\u0131<\/h3>\n<p>\u0130lk olarak Auth Server ile ileti\u015fim kuracak olan a\u015fa\u011f\u0131daki gibi bir servis olu\u015fturulmal\u0131d\u0131r.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nimport { Injectable } from '@angular\/core';\r\nimport * as oidc from &quot;oidc-client&quot;;\r\n\r\n@Injectable({\r\n  providedIn: 'root'\r\n})\r\nexport class AuthService {\r\n\r\n  config: oidc.UserManagerSettings = {\r\n    authority: &quot;https:\/\/localhost:1000&quot;,\r\n    client_id: &quot;AngularClient&quot;,\r\n    redirect_uri: &quot;http:\/\/localhost:4200\/callback&quot;,\r\n    post_logout_redirect_uri: &quot;http:\/\/localhost:4200&quot;,\r\n    response_type: &quot;code&quot;,\r\n    scope: &quot;Garanti.Write Garanti.Read profile openid email Roles&quot;\r\n  };\r\n\r\n  userManager: oidc.UserManager;\r\n  constructor() {\r\n    this.userManager = new oidc.UserManager(this.config)\r\n  }\r\n}\r\n<\/pre>\n<p>Olu\u015fturulan bu serviste &#8216;config&#8217; de\u011fi\u015fkeni bu client&#8217;\u0131n yetkiyi alaca\u011f\u0131 Auth Server&#8217;a dair t\u00fcm konfig\u00fcrasyonlar\u0131 bar\u0131nd\u0131rmaktad\u0131r. Detaylara bakarsak e\u011fer;<\/p>\n<ul>\n<li><strong><em>authority<\/em><\/strong><br \/>\nBu uygulaman\u0131n yetkiyi alaca\u011f\u0131 sunucunun yani Auth Server&#8217;\u0131n adresini tutar.\n<\/li>\n<li><strong><em>client_id<\/em><\/strong><br \/>\nClient&#8217;\u0131n Auth Server&#8217;da var olan tan\u0131mlamas\u0131ndaki client id de\u011ferini tutar.\n<\/li>\n<li><strong><em>redirect_uri<\/em><\/strong><br \/>\nClient do\u011fruland\u0131\u011f\u0131 zaman hangi adrese y\u00f6nlendirme yap\u0131laca\u011f\u0131n\u0131 tutar. Burada dikkat ederseniz &#8216;\/callback&#8217; adresine y\u00f6nlendirme ger\u00e7ekle\u015ftirilmektedir. Birazdan bu adresi kar\u015f\u0131layacak olan component olu\u015fturulacakt\u0131r.\n<\/li>\n<li><strong><em>post_logout_redirect_uri<\/em><\/strong><br \/>\n\u00c7\u0131k\u0131\u015f yap\u0131ld\u0131\u011f\u0131nda kullan\u0131c\u0131n\u0131n hangi adrese y\u00f6nlendirilece\u011fi tutulur.\n<\/li>\n<li><strong><em>response_type<\/em><\/strong><br \/>\nAk\u0131\u015f t\u00fcr\u00fcn\u00fc belirtir. &#8216;code&#8217;, authorization code&#8217;a kar\u015f\u0131l\u0131k gelir.\n<\/li>\n<li><strong><em>scope<\/em><\/strong><br \/>\nClient&#8217;\u0131n talep etti\u011fi scope&#8217;lard\u0131r. Bir MVC uygulamas\u0131na nazaran scope&#8217;lar olduk\u00e7a kolay talep edilebilmektedir. Scope&#8217;lar aras\u0131nda virg\u00fcl vs. gibi bir ayra\u00e7 olmaks\u0131z\u0131n direkt bo\u015fluk kullan\u0131lmal\u0131d\u0131r.\n<\/li>\n<\/ul>\n<p>Ard\u0131ndan client&#8217;\u0131n giri\u015f talebi neticesinde Auth Server&#8217;dan gelen sonucu kar\u015f\u0131layacak olan &#8216;callback&#8217; component&#8217;\u0131 olu\u015fturulmal\u0131 ve i\u00e7eri\u011fi a\u015fa\u011f\u0131daki gibi geli\u015ftirilmelidir.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nimport { Component, OnInit } from '@angular\/core';\r\nimport { Router } from '@angular\/router';\r\nimport * as oidc from 'oidc-client'\r\n\r\n@Component({\r\n  selector: 'app-callback',\r\n  templateUrl: '.\/callback.component.html',\r\n  styleUrls: &#x5B;'.\/callback.component.css']\r\n})\r\nexport class CallbackComponent implements OnInit {\r\n\r\n  constructor(private router: Router) { }\r\n\r\n  ngOnInit(): void {\r\n    new oidc.UserManager({ response_mode: &quot;query&quot; })\r\n      .signinRedirectCallback().then(() =&gt; {\r\n        this.router.navigateByUrl(&quot;\/&quot;);\r\n      });\r\n  }\r\n}\r\n<\/pre>\n<p>Burada dikkat ederseniz, &#8216;UserManager&#8217; nesnesinin constructor&#8217;\u0131 \u00fczerinde &#8216;response_mode&#8217; alan\u0131n\u0131n &#8216;query&#8217; olarak i\u015faretlendi\u011fini g\u00f6r\u00fcyorsunuz. Bu, client&#8217;\u0131n talebi neticesinde authorization endpoint&#8217;e g\u00f6nderilecek iste\u011fe kar\u015f\u0131l\u0131k gelecek olan datalar\u0131n nereden ta\u015f\u0131naca\u011f\u0131n\u0131 belirtmektedir. &#8216;query&#8217;, Query String&#8217;e kar\u015f\u0131l\u0131k gelmektedir. Velhas\u0131l bu component Auth Server&#8217;dan gelen sonucu kar\u015f\u0131lad\u0131ktan sonra art\u0131k kullan\u0131c\u0131y\u0131 bir sayfaya y\u00f6nlendirmelidir. Misal bizler bu \u00f6rne\u011fimizde ana sayfaya(\/) y\u00f6nlendirmekteyiz. Lakin bu y\u00f6nlendirmenin yap\u0131laca\u011f\u0131 adresin ihtiyaca binaen \u00f6zel olarak belirlenmesi en do\u011frusu olacakt\u0131r.<\/p>\n<p>Ve son olarak giri\u015f yapma i\u015flemleri i\u00e7in olu\u015fturulan bir component(home) i\u00e7erisinde a\u015fa\u011f\u0131daki \u00e7al\u0131\u015fmalar yap\u0131lmal\u0131d\u0131r.<br \/>\n<em>home.component.html<\/em><\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n&lt;h4&gt;{{message}}&lt;\/h4&gt;\r\n&lt;button (click)=&quot;login()&quot;&gt;Login&lt;\/button&gt; | &lt;button (click)=&quot;logout()&quot;&gt;Logout&lt;\/button&gt;\r\n&lt;br&gt;\r\n&lt;h3 style=&quot;color:red;&quot;&gt;{{bankaData}}&lt;\/h3&gt;\r\n<\/pre>\n<p><em>home.component.js<\/em><\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nimport { Component, OnInit } from '@angular\/core';\r\nimport { AuthService } from 'src\/app\/services\/auth.service';\r\nimport { HttpClient } from '@angular\/common\/http';\r\n\r\n@Component({\r\n  selector: 'app-home',\r\n  templateUrl: '.\/home.component.html',\r\n  styleUrls: &#x5B;'.\/home.component.css']\r\n})\r\nexport class HomeComponent implements OnInit {\r\n\r\n  constructor(private authService: AuthService, private httpClient: HttpClient) { }\r\n\r\n  message: string;\r\n  bankaData: any = &quot;Ba\u011flant\u0131 sa\u011flanamad\u0131...&quot;;\r\n  ngOnInit(): void {\r\n    this.authService.userManager.getUser().then(user =&gt; {\r\n      \/\/Kullan\u0131c\u0131 login olduysa buras\u0131 tetiklenecek.\r\n      if (user) {\r\n        console.log(user);\r\n        localStorage.setItem(&quot;accessToken&quot;, user.access_token)\r\n        this.message = &quot;Giri\u015f ba\u015far\u0131l\u0131...&quot;;\r\n      }\r\n      else\r\n        this.message = &quot;Giri\u015f ba\u015far\u0131s\u0131z...&quot;;\r\n    }).then(() =&gt; this.httpClient.get(&quot;https:\/\/localhost:2000\/api\/banka&quot;, {\r\n      headers:{&quot;Authorization&quot;:&quot;Bearer &quot; + localStorage.getItem(&quot;accessToken&quot;)}\r\n    }).subscribe(data =&gt; this.bankaData = data));\r\n  }\r\n\r\n  login() {\r\n    this.authService.userManager.signinRedirect();\r\n  }\r\n\r\n  logout() {\r\n    this.authService.userManager.signoutRedirect();\r\n  }\r\n}\r\n<\/pre>\n<p>G\u00f6r\u00fcld\u00fc\u011f\u00fc \u00fczere kullan\u0131c\u0131 ilgili component&#8217;a istek g\u00f6nderdi\u011fi taktirde biraz \u00f6nce olu\u015fturulan &#8216;AuthService&#8217; \u00fczerinden Auth Server&#8217;a bir istek g\u00f6nderilecek ve &#8216;getUser()&#8217; fonksiyonu ile istek neticesinde giri\u015f yapan kullan\u0131c\u0131 elde edilecektir. Bu \u00f6rnekte gelen kullan\u0131c\u0131n\u0131n access token de\u011feri local storage&#8217;a yazd\u0131r\u0131lmakta ve ilgili API&#8217;a istek yap\u0131l\u0131rken tekrar local storage&#8217;dan elde edilerek header&#8217;a yerle\u015ftirilip istek g\u00f6nderilmektedir.<\/p>\n<p>Ayr\u0131ca &#8216;login&#8217; fonksiyonunda Auth Server \u00fczerinden &#8216;signinRedirect()&#8217; fonksiyonu ile giri\u015f talebi yap\u0131lmakta ve ilgili serviste yap\u0131lan konfig\u00fcrasyonlara uygun Auth Server&#8217;a gerekli y\u00f6nlendirmeler sa\u011flanmaktad\u0131r. Tabi giri\u015f i\u015flemi ba\u015far\u0131yla sa\u011fland\u0131\u011f\u0131 taktirde kullan\u0131c\u0131 konfig\u00fcrasyonlarda belirtilen geri d\u00f6n\u00fc\u015f adresine y\u00f6nlendirilmektedir. Benzer \u015fekilde i\u015flevsellik &#8216;logout&#8217; fonksiyonu i\u00e7inde ge\u00e7erlidir ve \u00e7\u0131k\u0131\u015f i\u015flemi sa\u011fland\u0131ktan sonra belirtilen adrese y\u00f6nlendirme yap\u0131lmaktad\u0131r.<\/p>\n<p>Bu geli\u015ftirmelerden sonra ilgili component&#8217;lar\u0131n rotalar\u0131n\u0131 da kodlarda belirtilenlere uygun olarak a\u015fa\u011f\u0131daki gibi belirleyebiliriz;<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nimport { NgModule } from '@angular\/core';\r\nimport { Routes, RouterModule } from '@angular\/router';\r\nimport { CallbackComponent } from '.\/components\/callback\/callback.component';\r\nimport { HomeComponent } from '.\/components\/home\/home.component';\r\nimport { SilentCallbackComponent } from '.\/components\/silent-callback\/silent-callback.component';\r\n\r\nconst routes: Routes = &#x5B;\r\n  {\r\n    path: '', component: HomeComponent\r\n  },\r\n  {\r\n    path: &quot;callback&quot;, component: CallbackComponent\r\n  }\r\n];\r\n\r\n@NgModule({\r\n  imports: &#x5B;RouterModule.forRoot(routes)],\r\n  exports: &#x5B;RouterModule]\r\n})\r\nexport class AppRoutingModule { }\r\n<\/pre>\n<p>Nihai olarak uygulamay\u0131 derleyip, \u00e7al\u0131\u015ft\u0131r\u0131rsak e\u011fer a\u015fa\u011f\u0131daki ekran g\u00f6r\u00fcnt\u00fcs\u00fcnde oldu\u011fu gibi s\u0131k\u0131nt\u0131s\u0131z yetkilendirmenin yap\u0131ld\u0131\u011f\u0131n\u0131 g\u00f6rebilmekteyiz.<br \/>\n<a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi.gif\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi.gif\" alt=\"IdentityServer4 Yaz\u0131 Serisi #21 - Angular \u0130le IdentityServer4 Kullan\u0131m\u0131\" width=\"800\" height=\"650\" class=\"aligncenter size-full wp-image-19514\" \/><\/a><br \/>\nAyriyetten Angular uygulamas\u0131n\u0131n Auth Server&#8217;dan ald\u0131\u011f\u0131 cevab\u0131 konsolda incelersek e\u011fer;<br \/>\n<a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi-2.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi-2.jpg\" alt=\"IdentityServer4 Yaz\u0131 Serisi #21 - Angular \u0130le IdentityServer4 Kullan\u0131m\u0131\" width=\"1749\" height=\"539\" class=\"aligncenter size-full wp-image-19524\" srcset=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi-2.jpg 1749w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi-2-300x92.jpg 300w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi-2-1024x316.jpg 1024w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi-2-768x237.jpg 768w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi-2-1536x473.jpg 1536w\" sizes=\"auto, (max-width: 1749px) 100vw, 1749px\" \/><\/a><br \/>\ng\u00f6r\u00fcld\u00fc\u011f\u00fc \u00fczere access token de\u011ferinden, profil bilgilerine, client&#8217;\u0131n t\u00fcm scope&#8217;lar\u0131ndan, token type&#8217;\u0131na kadar t\u00fcm bilgiler gelmektedir.<\/p>\n<h3 style=\"color:#e83e8c;\"><del>Refresh Token<\/del>(Silent Refresh) Kullan\u0131m\u0131<\/h3>\n<p>UI tabanl\u0131 client&#8217;lar da yap\u0131lan kimlik do\u011frulama i\u015flemleri server side client&#8217;lara nazaran daha y\u00fcksek g\u00fcvenlik riski ta\u015f\u0131makta olduklar\u0131ndan dolay\u0131 refresh token gibi hassas verilerin taray\u0131c\u0131da tutulmas\u0131na pek g\u00fcvenilememekte ve bundan dolay\u0131 OpenId Connect protokol\u00fcyle yap\u0131lan \u00e7al\u0131\u015fmalarda client&#8217;ta SPA uygulamas\u0131 varsa refresh token kullan\u0131lmamaktad\u0131r. Nihayetinde SPA uygulamas\u0131 \u00fczerinden k\u00f6t\u00fc niyetli kullan\u0131c\u0131lar, bir vatanda\u015f\u0131n refresh token de\u011ferini elde ederek, Auth Server \u00fczerinden sisteme ba\u011fl\u0131 t\u00fcm client&#8217;lar\u0131 s\u00f6m\u00fcrebilme \u015fans\u0131n\u0131z elde edebilecektir. B\u00f6yle bir ihtimal sonucunda do\u011fabilecek zarureti var\u0131n siz d\u00fc\u015f\u00fcn\u00fcn. Haliyle taraf\u0131m\u0131zca g\u00f6ze al\u0131namayacak derecede b\u00fcy\u00fck bir risk arz eden bu durum i\u00e7in refresh token kullanmamay\u0131 tercih etmekyiz&#8230;<\/p>\n<p><strong><em>Peki hoca, refresh token kullanmayacaksak ne kullanaca\u011f\u0131z?<\/em><\/strong> \u015feklinde sordu\u011funuzu duyar gibiyim&#8230; Evet, ara ba\u015fl\u0131k olarak refresh token de\u011ferini yazm\u0131\u015f olabilirim lakin teknik boyutta kullanaca\u011f\u0131m\u0131z yap\u0131lanma refresh token yerine Silent Refresh(Sessiz Yenileme) yap\u0131lanmas\u0131d\u0131r.<\/p>\n<p><strong style=\"color:purple;\">Silent Refresh<\/strong>, t\u0131pk\u0131 refresh token gibi kullan\u0131c\u0131 deneyimi a\u00e7\u0131s\u0131ndan s\u00fcreci efektif hale getirebilmek i\u00e7in geli\u015ftirilmi\u015f bir stratejidir. Kullan\u0131c\u0131y\u0131 yeni bir yetkili access token edinebilmesi i\u00e7in istek\/login sayfas\u0131na y\u00f6nlendirmeye gerek kalmaks\u0131z\u0131n a\u00e7\u0131k olan oturum \u00fczerinden otomatik access token almas\u0131n\u0131 sa\u011flayan ve bunu arkaplanda, *sessizce* ger\u00e7ekle\u015ftiren bir yap\u0131lanmad\u0131r.<\/p>\n<p>Bu yap\u0131lanmay\u0131 aktifle\u015ftirebilmek i\u00e7in ilk olarak Auth Server&#8217;daki &#8216;Config.cs&#8217; dosyas\u0131nda tan\u0131ml\u0131 olan client \u00fczerinde a\u015fa\u011f\u0131daki konfig\u00fcrasyon yap\u0131lmal\u0131d\u0131r.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n        #region Clients\r\n        public static IEnumerable&lt;Client&gt; GetClients() =&gt;\r\n            new List&lt;Client&gt;\r\n            {\r\n                new Client\r\n                {\r\n                    ClientId = &quot;AngularClient&quot;,\r\n                    .\r\n                    .\r\n                    .\r\n                    RedirectUris = {&quot;http:\/\/localhost:4200\/callback&quot;, &quot;http:\/\/localhost:4200\/silent-callback&quot;},\r\n                    AccessTokenLifetime = 70\r\n                }\r\n            };\r\n        #endregion\r\n<\/pre>\n<p>Yukar\u0131daki kod blo\u011funu incelerseniz e\u011fer client&#8217;a <code style=\"color:red;font-size:10px;\">http:\/\/localhost:4200\/silent-callback<\/code> d\u00f6n\u00fc\u015f adresi eklenmi\u015ftir. Silent refresh yap\u0131lanmas\u0131 ile yap\u0131lacak talebin bilgileri ve konfig\u00fcrasyonlar\u0131 i\u00e7in kullan\u0131c\u0131 bu adrese y\u00f6nlendirilecek ve hangi server&#8217;a(auth server), hangi response_type t\u00fcr\u00fcnde istek yap\u0131laca\u011f\u0131n\u0131 ve hangi scope&#8217;lar\u0131n talep edilece\u011fini bu adreste bildirece\u011fiz. Yani birazdan Angular uygulamas\u0131nda bu adrese kar\u015f\u0131l\u0131k gelen bir component olu\u015fturacak ve bahsedilen t\u00fcm bu i\u015flemleri icra edece\u011fiz. &#8216;AccessTokenLifetime&#8217; \u00f6zelli\u011fine gelirsek e\u011fer, silent refresh yap\u0131lanmas\u0131 kendisini access token \u00f6mr\u00fcn\u00fcn bitimine her 60 saniye kald\u0131\u011f\u0131nda tetiklemektedir bundan dolay\u0131 olu\u015fturulacak access token s\u00fcresini 70 saniye olarak belirliyoruz.<\/p>\n<p>Auth server&#8217;da ki konfig\u00fcrasyondan sonra y\u00f6n\u00fcm\u00fcz\u00fc Angular&#8217;a \u00e7evirelim ve olu\u015fturdu\u011fumuz &#8216;AuthService&#8217; isimli servisimizde tan\u0131mlanan konfig\u00fcrasyona auth server&#8217;da tan\u0131mlanan silent redirect url&#8217;i a\u015fa\u011f\u0131daki gibi bildirelim.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nimport { Injectable } from '@angular\/core';\r\nimport * as oidc from &quot;oidc-client&quot;;\r\n\r\n@Injectable({\r\n  providedIn: 'root'\r\n})\r\nexport class AuthService {\r\n\r\n  config: oidc.UserManagerSettings = {\r\n    .\r\n    .\r\n    .\r\n    automaticSilentRenew: true,\r\n    silent_redirect_uri: &quot;http:\/\/localhost:4200\/silent-callback&quot;\r\n  };\r\n\r\n  userManager: oidc.UserManager;\r\n  constructor() {\r\n    this.userManager = new oidc.UserManager(this.config);\r\n  }\r\n}\r\n<\/pre>\n<p>B\u00f6ylece bu serviste olu\u015fturulan userManager nesnesi &#8216;automaticSilentRenew&#8217; sayesinde silent refresh operasyonunu aktifle\u015ftirece\u011fini ve gerekli konfig\u00fcrasyonlar i\u00e7in kullan\u0131c\u0131y\u0131 &#8216;silent_redirect_uri&#8217; adresine y\u00f6nlendirece\u011fini bilmektedir. Haliyle \u015fimdi &#8216;silent-callback&#8217; adresini kar\u015f\u0131layacak olan component&#8217;\u0131 geli\u015ftirelim.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nimport { Component, OnInit } from '@angular\/core';\r\nimport * as oidc from 'oidc-client';\r\n\r\n@Component({\r\n  selector: 'app-silent-callback',\r\n  templateUrl: '.\/silent-callback.component.html',\r\n  styleUrls: &#x5B;'.\/silent-callback.component.css']\r\n})\r\nexport class SilentCallbackComponent implements OnInit {\r\n\r\n  constructor() { }\r\n\r\n  config: oidc.UserManagerSettings = {\r\n    authority: &quot;https:\/\/localhost:1000&quot;,\r\n    client_id: &quot;AngularClient&quot;,\r\n    response_type: &quot;code&quot;,\r\n    scope: &quot;Garanti.Write Garanti.Read profile openid email Roles&quot;,\r\n  }\r\n\r\n  ngOnInit(): void {\r\n    new oidc.UserManager(this.config).signinSilentCallback()\r\n      .catch(error =&gt; console.log(error));\r\n  }\r\n}\r\n<\/pre>\n<p>Burada g\u00f6r\u00fcld\u00fc\u011f\u00fc \u00fczere &#8216;UserManager&#8217; nesnesi yeni bir access token&#8217;a odakl\u0131 konfig\u00fcrasyonla &#8216;signinSilentCallback&#8217; fonksiyonunu tetiklemektedir ve b\u00f6ylece bu fonksiyon ile yeni bir access token talep edilmektedir.<\/p>\n<p>Yap\u0131lan bu \u00e7al\u0131\u015fma neticesinde uygulamay\u0131 derleyip, \u00e7al\u0131\u015ft\u0131rd\u0131\u011f\u0131m\u0131zda i\u015fleyi\u015fi a\u015fa\u011f\u0131daki gibi g\u00f6zlemleyebilmekteyiz.<br \/>\n<a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi-1.gif\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/IdentityServer4-Yazi-Serisi-21-Angular-Ile-IdentityServer4-Kullanimi-1.gif\" alt=\"IdentityServer4 Yaz\u0131 Serisi #21 - Angular \u0130le IdentityServer4 Kullan\u0131m\u0131\" width=\"800\" height=\"475\" class=\"aligncenter size-full wp-image-19555\" \/><\/a><br \/>\nDikkat ederseniz e\u011fer silent refresh ile yap\u0131lan istek neticesinde elde edilen token bir sonraki <code>...userManager.getUser()...<\/code> talepte taray\u0131c\u0131ya fiziksel olarak g\u00f6nderilmektedir. Client gelen bu yeni token&#8217;\u0131 ilgili storage&#8217;lara depolad\u0131ktan sonra isteklerine yeni access token ile devam edebilmekte ve kesintiye u\u011framaks\u0131z\u0131n i\u015flemlerini ger\u00e7ekle\u015ftirebilmektedir.<\/p>\n<p>Uzun ve zahmetli bir yaz\u0131 oldu&#8230; Umar\u0131m konuya dair meram\u0131n\u0131z\u0131 giderecek bir i\u00e7erik ortaya koyabilmi\u015fimdir \ud83d\ude42<\/p>\n<p>\u0130lgilenenlerin faydalanmas\u0131 dile\u011fiyle&#8230;<br \/>\nSonraki yaz\u0131lar\u0131mda g\u00f6r\u00fc\u015fmek \u00fczere&#8230;<br \/>\n\u0130yi \u00e7al\u0131\u015fmalar&#8230;<\/p>\n<p>Not : \u00d6rnek uygulamalar\u0131 indirmek i\u00e7in a\u015fa\u011f\u0131daki linklere t\u0131klay\u0131n\u0131z.<br \/>\n<a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/AngularClient.zip\">AngularClient<\/a><br \/>\n<a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2020\/11\/SPAClientIdentityServer4Example.zip\">SPAClientIdentityServer4Example<\/a><\/p>\n<!-- AddThis Advanced Settings generic via filter on the_content --><!-- AddThis Share Buttons generic via filter on the_content -->","protected":false},"excerpt":{"rendered":"<p>Merhaba, IdentityServer4 Yaz\u0131 Serisinin bu yirmibirinci makalesinde art\u0131k IdentityServer4 framework&#8217;\u00fc ile bir SPA(Single Page Application) uygulamas\u0131n\u0131n nas\u0131l kullan\u0131ld\u0131\u011f\u0131na s\u0131ra geldi\u011fini d\u00fc\u015f\u00fcnerek Angular ile \u00f6rneklendirmeye karar verdim. SPA teknolojilerinden Angular mimarisinde ele alaca\u011f\u0131m\u0131z bu i\u00e7eri\u011fimizde&#46;&#46;&#46;<!-- AddThis Advanced Settings generic via filter on get_the_excerpt --><!-- AddThis Share Buttons generic via filter on get_the_excerpt --><\/p>\n","protected":false},"author":1,"featured_media":18168,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2420,3826],"tags":[2156,3967,3971,3974,3968,3972,3975,3827,3966,3970,3965,3973,3976,3969],"class_list":["post-19472","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-angular","category-identityserver4","tag-angular","tag-angular-identityserver4","tag-angular-identityserver4-refresh-token","tag-angular-identityserver4-silent-refresh","tag-angular-oidc-client","tag-angular-refresh-token","tag-angular-silent-refresh","tag-identityserver4","tag-identityserver4-angular","tag-identityserver4-ui","tag-oidc-client","tag-silent-refresh","tag-silent-refresh-nedir","tag-spa-identityserver4"],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/posts\/19472","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/comments?post=19472"}],"version-history":[{"count":77,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/posts\/19472\/revisions"}],"predecessor-version":[{"id":19559,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/posts\/19472\/revisions\/19559"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/media\/18168"}],"wp:attachment":[{"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/media?parent=19472"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/categories?post=19472"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/tags?post=19472"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}