{"id":2855,"date":"2014-07-09T22:21:34","date_gmt":"2014-07-10T05:21:34","guid":{"rendered":"http:\/\/www.cloudidentity.com\/blog\/?p=2855"},"modified":"2014-07-11T17:46:11","modified_gmt":"2014-07-12T00:46:11","slug":"the-new-token-cache-in-adal-v2","status":"publish","type":"post","link":"https:\/\/www.cloudidentity.com\/blog\/2014\/07\/09\/the-new-token-cache-in-adal-v2\/","title":{"rendered":"The New Token Cache in ADAL v2"},"content":{"rendered":"<p><a href=\"https:\/\/www.cloudidentity.com\/blog\/wp-content\/uploads\/2014\/07\/image4.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border: 0px;\" title=\"image\" src=\"https:\/\/www.cloudidentity.com\/blog\/wp-content\/uploads\/2014\/07\/image_thumb4.png\" alt=\"image\" width=\"640\" height=\"389\" border=\"0\" \/><\/a><\/p>\n<p>The release candidate of ADAL v2 introduces a new cache model, which makes possible to cache tokens in middle tier apps and dramatically simplifies creating custom caches.<br \/>\nIn this post I&#8217;ll give you\u00a0 a brief introduction to the new model. You can see the new cache in action in our <a href=\"https:\/\/github.com\/AzureADSamples\/\">updates samples<\/a>.<\/p>\n<h2>Limitations of the ADAL v1 Model<\/h2>\n<p>The token cache in ADAL plays a key role in keeping the programming model approachable. The fact that ADAL saves tokens (of all kinds: access, refresh, id) and token metadata (requesting client, target resource, user who obtained the token, tenant, whether the refresh token is an MRRT\u2026) allows you to simply keep calling AcquireToken, knowing that behind the scenes ADAL will make the absolute best use of the cached information to give you the token you need while minimizing prompts.<\/p>\n<p>Another role fulfilled by the ADAL cache is to offer you a view on the current authentication status of your application: by interrogating the cache you can discover whether you have access to a given resource, if you have tokens for a specific set of user accounts, and so on.<\/p>\n<p>In ADAL v1, the cache implementation reflected the primary target scenarios of that version of the library: native clients. The cache implemented as an IDictionary, with a specific key type which reflected the domain-specific info necessary for handling tokens. That fulfilled the two functions outlined earlier, keeping track of the data we needed and offering an easy way of querying the collection (via LINQ). That did its job for native clients, however that was unsuitable for use on mid-tier apps. Think of a web app with few millions of users, each of them with few tokens stored for calling API in the context of their sessions \u2013 the resulting dictionary would have been pretty hard to scale. For that reason, AcquireToken* implementations in ADAL v1 meant to prevalently run on the server side did not make use of ADAL\u2019s cache.<\/p>\n<p>Another shortcoming of the v1 model was that providing a custom cache required you to implement IDictionary, not rocket science but certainly an onerous task. Furthermore: many of the elements in the key type were really meant for your own queries and were never used by AcquireToken. We were aware of the complications, but given the asymmetry between producers of custom cache implementations and consumers of such classes (the latter vastly outnumbering the former) we made the tradeoff. When this was picked up in v2, though our awesome dev team found a way of avoiding the tradeoff altogether \u2013 designing a new model that delivers on both functions AND that is a breeze to implement!<\/p>\n<h2>The New Cache Model<\/h2>\n<p>The idea of the new cache model is pretty simple: ADAL manages the cache structure as a private implementation detail, but gives you the means to 1) provide a persistence layer for it, so that you can use your favorite store to hold it and 2) it still allows you to obtain views of the cache content, so that you can gain insights on the capabilities of your application without being exposed to the internals to how the various cache entries are actually maintained. It sounds pretty awesome, right? <img decoding=\"async\" class=\"wlEmoticon wlEmoticon-smile\" src=\"https:\/\/www.cloudidentity.com\/blog\/wp-content\/uploads\/2014\/07\/wlEmoticon-smile2.png\" alt=\"Smile\" \/><\/p>\n<p>You create a custom cache by deriving from the new TokenCache class, and passing an instance of such class at AuthenticationContext construction time. Here there\u2019s how it looks like in VS\u2019 Class View:<\/p>\n<p><a href=\"https:\/\/www.cloudidentity.com\/blog\/wp-content\/uploads\/2014\/07\/image3.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border: 0px;\" title=\"image\" src=\"https:\/\/www.cloudidentity.com\/blog\/wp-content\/uploads\/2014\/07\/image_thumb3.png\" alt=\"image\" width=\"577\" height=\"327\" border=\"0\" \/><\/a><\/p>\n<p>There\u2019s quite a lot of stuff there, but in fact you need to touch only 3-4 things.<br \/>\nHere there\u2019s how it works.<\/p>\n<p>TokenCache features three notifications, <strong>BeforeAccess<\/strong>, <strong>BeforeWrite<\/strong> and <strong>AfterAccess<\/strong>, that are activated whenever ADAL does work against the cache. Those notifications offer you the opportunity of keeping in sync the storage of your choice with the in-memory cache that ADAL uses.<\/p>\n<p>Say that you start your app for the very first time, and you make a call to AcquireToken(resource1, clientId, redirectUri). From the cache\u2019s point of view, how does that unfold?<\/p>\n<ol>\n<li>ADAL needs to check the cache to see if there is already an access token for resource1 obtained by client1, or if there is a refresh token good for obtaining such an access token, and whatever other private heuristic you don\u2019t need to worry about. Right before it reads the cache, ADAL calls the <strong>BeforeAccess <\/strong>notification. Here, you have the opportunity of retrieving your persisted cache blob from wherever you chose to save it, and pump it in ADAL. You do so by passing that blob to <strong>Deserialize<\/strong>.<br \/>\nNote that you can apply all kind of heuristics to decide whether the existing inmemory copy is still OK to reduce the times in which you access your persistent store.<\/li>\n<li>As we said, this is the first time that the application runs: hence the cache will (typically) be empty. Hence, ADAL pops out the authentication UX and guides the user through the authentication experience. Once it obtains a new token, it needs to save it in the cache: but right before that, it invokes the <strong>BeforeWrite<\/strong> notification. That gives you the opportunity of applying whatever concurrency strategy you want to enact: for example, on a mid tier app you might decide to place a lock on your blob \u2013 so that other nodes in your farm possibly attempting a write at the same time would avoid producing conflicting updates. If you are optimistic, of course you can decide to simply do nothing heer :<img decoding=\"async\" class=\"wlEmoticon wlEmoticon-winkingsmile\" src=\"https:\/\/www.cloudidentity.com\/blog\/wp-content\/uploads\/2014\/07\/wlEmoticon-winkingsmile1.png\" alt=\"Winking smile\" \/><\/li>\n<li>After ADAL added the new token in its in-memory copy of the cache, it calls the <strong>AfterAccess <\/strong>notification. That notification is in fact called every time ADAL accessed the cache, not just when a write took place: however you can always tell if the current operation resulted in a cache change, as in that case the property <strong>HasStateChanged <\/strong>will be set to true. If that is the case, you will typically call <strong>Serialize()<\/strong> to get a binary blob representing the latest cache content \u2013 and save it in your storage. After that, it will be your responsibility to clear whatever lock you might have set.<br \/>\n<strong>Very important<\/strong>: <em><span style=\"text-decoration: underline;\">ADAL NEVER automatically reset HasStateChanged to false<\/span><\/em>. <span style=\"text-decoration: underline;\"><em>You have to do it in your own code <\/em><\/span>once you are satisfied that you handled the event correctly.<\/li>\n<\/ol>\n<p>Those are the main moving parts you need to handle. Other important aspects to consider concern the lifecycle of the cache instance outside of its use from AcquireToken. For example, you\u2019ll likely want to populate the cache from your store at construction time; you\u2019ll want to override <strong>Clear<\/strong> and <strong>DeleteItem<\/strong> to ensure that you reflect cache state changes; and so on.<\/p>\n<p>You might wonder why you can\u2019t just wait for the first access and leave to the notifications to do the first initialization. That\u2019s tricky. You could do that, but then if you\u2019d need to query the cache before requesting the first token you\u2019d be in trouble. Think of a multi-tenant client: on first access you\u2019ll use common as the authority, but for subsequent accesses you want to use the authority corresponding to the user that actually signed in and initialized the app to its own tenant. If you don\u2019t do that, you\u2019ll never hit the cache during AcquireToken given that using \u201ccommon\u201d is equivalent to say \u201cI don\u2019t know what tenant to use\u201d.<\/p>\n<p>If you want to query the cache, you can call <strong>ReadItems()<\/strong> to pull out an IEnumerable of <strong>TokenCacheItems<\/strong>.<\/p>\n<p>Pretty easy, right?<\/p>\n<p>Here there\u2019s one of my favorite benefits of this model: from ADAL v2 RC on, <strong>AcquireTokenByAuthorizationCode <\/strong>does save tokens in the cache! This will result in a tremendous reduction in the amount of code that it is necessary on middle tier applications, as you\u2019ll see in the updated samples.<\/p>\n<h2>Examples<\/h2>\n<p>Here there\u2019s a simple example. Say that I am writing a Windows desktop app, and I want to save tokens so that I don\u2019t have to re-authenticate every single time I launch the application. I decide to save the tokens in a DPAPI-protected file. Here there\u2019s a super simple cache implementation doing that:<\/p>\n<pre class=\"csharpcode\"><span class=\"rem\">\/\/ This is a simple persistent cache implementation for a desktop application.<\/span>\r\n<span class=\"rem\">\/\/ It uses DPAPI for storing tokens in a local file.<\/span>\r\n<span class=\"kwrd\">class<\/span> FileCache : TokenCache\r\n{\r\n    <span class=\"kwrd\">public<\/span> <span class=\"kwrd\">string<\/span> CacheFilePath;\r\n    <span class=\"kwrd\">private<\/span> <span class=\"kwrd\">static<\/span> <span class=\"kwrd\">readonly<\/span> <span class=\"kwrd\">object<\/span> FileLock = <span class=\"kwrd\">new<\/span> <span class=\"kwrd\">object<\/span>();\r\n\r\n    <span class=\"rem\">\/\/ Initializes the cache against a local file.<\/span>\r\n    <span class=\"rem\">\/\/ If the file is already present, it loads its content in the ADAL cache<\/span>\r\n    <span class=\"kwrd\">public<\/span> FileCache(<span class=\"kwrd\">string<\/span> filePath=<span class=\"str\">@\".\\TokenCache.dat\"<\/span>)\r\n    {\r\n        CacheFilePath = filePath;\r\n        <span class=\"kwrd\">this<\/span>.AfterAccess = AfterAccessNotification;\r\n        <span class=\"kwrd\">this<\/span>.BeforeAccess = BeforeAccessNotification;\r\n        <span class=\"kwrd\">lock<\/span> (FileLock)\r\n        {\r\n            <span class=\"kwrd\">this<\/span>.Deserialize(File.Exists(CacheFilePath) ? \r\n                ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath), \r\n                                        <span class=\"kwrd\">null<\/span>, \r\n                                        DataProtectionScope.CurrentUser) \r\n                : <span class=\"kwrd\">null<\/span>);\r\n        }\r\n    }\r\n\r\n    <span class=\"rem\">\/\/ Empties the persistent store.<\/span>\r\n    <span class=\"kwrd\">public<\/span> <span class=\"kwrd\">override<\/span> <span class=\"kwrd\">void<\/span> Clear()\r\n    {\r\n        <span class=\"kwrd\">base<\/span>.Clear();\r\n        File.Delete(CacheFilePath);\r\n    }\r\n\r\n    <span class=\"rem\">\/\/ Triggered right before ADAL needs to access the cache.<\/span>\r\n    <span class=\"rem\">\/\/ Reload the cache from the persistent store in case it changed since the last access.<\/span>\r\n     <span class=\"kwrd\">void<\/span> BeforeAccessNotification(TokenCacheNotificationArgs args)\r\n    {\r\n        <span class=\"kwrd\">lock<\/span> (FileLock)\r\n        {\r\n            <span class=\"kwrd\">this<\/span>.Deserialize(File.Exists(CacheFilePath) ?  \r\n                ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),\r\n                                        <span class=\"kwrd\">null<\/span>,\r\n                                        DataProtectionScope.CurrentUser) \r\n                : <span class=\"kwrd\">null<\/span>);\r\n        }\r\n    }\r\n\r\n    <span class=\"rem\">\/\/ Triggered right after ADAL accessed the cache.<\/span>\r\n    <span class=\"kwrd\">void<\/span> AfterAccessNotification(TokenCacheNotificationArgs args)\r\n    {\r\n        <span class=\"rem\">\/\/ if the access operation resulted in a cache update<\/span>\r\n        <span class=\"kwrd\">if<\/span> (<span class=\"kwrd\">this<\/span>.HasStateChanged)\r\n        {\r\n            <span class=\"kwrd\">lock<\/span> (FileLock)\r\n            {                    \r\n                <span class=\"rem\">\/\/ reflect changes in the persistent store<\/span>\r\n                File.WriteAllBytes(CacheFilePath, \r\n                    ProtectedData.Protect(<span class=\"kwrd\">this<\/span>.Serialize(),\r\n                                            <span class=\"kwrd\">null<\/span>,\r\n                                            DataProtectionScope.CurrentUser));\r\n                <span class=\"rem\">\/\/ once the write operation took place, restore the HasStateChanged bit to false<\/span>\r\n                <span class=\"kwrd\">this<\/span>.HasStateChanged = <span class=\"kwrd\">false<\/span>;\r\n            }                \r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>That is really super-simple code, if compared to having to implement an entire IDictionary.<\/p>\n<p>Want to see something a bit more challenging?<\/p>\n<p>Say that I have a web application. My web app connects to web APIs on behalf of its users. Every user has a set of tokens that are saved in a SQL DB, so that when they sign in to the web app they can directly perform their web API calls without having to re-authenticate\/repeat consent. The new ADAL cache model makes it pretty easy to achieve this: we can have a flat list of blobs, all representing an ADAL cache for a specific web user. When the user signs in, we retrieve the corresponding blob and use it to initialize his\/her token cache. That\u2019s exactly how we implemented the cache in the updated <a href=\"https:\/\/github.com\/AzureADSamples\/?query=multite\">multitenant samples<\/a>. Here there\u2019s the implementation:<\/p>\n<pre class=\"csharpcode\"><span class=\"kwrd\">public<\/span> <span class=\"kwrd\">class<\/span> PerWebUserCache\r\n{\r\n    [Key]\r\n    <span class=\"kwrd\">public<\/span> <span class=\"kwrd\">int<\/span> EntryId { get; set; }\r\n    <span class=\"kwrd\">public<\/span> <span class=\"kwrd\">string<\/span> webUserUniqueId { get; set; }\r\n    <span class=\"kwrd\">public<\/span> <span class=\"kwrd\">byte<\/span>[] cacheBits { get; set; }\r\n    <span class=\"kwrd\">public<\/span> DateTime LastWrite { get; set; }\r\n}\r\n\r\n<span class=\"kwrd\">public<\/span> <span class=\"kwrd\">class<\/span> EFADALTokenCache: TokenCache\r\n{\r\n    <span class=\"kwrd\">private<\/span> TodoListWebAppContext db = <span class=\"kwrd\">new<\/span> TodoListWebAppContext();\r\n    <span class=\"kwrd\">string<\/span> User;\r\n    PerWebUserCache Cache;\r\n    \r\n    <span class=\"rem\">\/\/ constructor<\/span>\r\n    <span class=\"kwrd\">public<\/span> EFADALTokenCache(<span class=\"kwrd\">string<\/span> user)\r\n    {\r\n       <span class=\"rem\">\/\/ associate the cache to the current user of the web app<\/span>\r\n        User = user;\r\n        \r\n        <span class=\"kwrd\">this<\/span>.AfterAccess = AfterAccessNotification;\r\n        <span class=\"kwrd\">this<\/span>.BeforeAccess = BeforeAccessNotification;\r\n        <span class=\"kwrd\">this<\/span>.BeforeWrite = BeforeWriteNotification;\r\n\r\n        <span class=\"rem\">\/\/ look up the entry in the DB<\/span>\r\n        Cache = db.PerUserCacheList.FirstOrDefault(c =&gt; c.webUserUniqueId == User);\r\n        <span class=\"rem\">\/\/ place the entry in memory<\/span>\r\n        <span class=\"kwrd\">this<\/span>.Deserialize((Cache == <span class=\"kwrd\">null<\/span>) ? <span class=\"kwrd\">null<\/span> : Cache.cacheBits);\r\n    }\r\n\r\n    <span class=\"rem\">\/\/ clean up the DB<\/span>\r\n    <span class=\"kwrd\">public<\/span> <span class=\"kwrd\">override<\/span> <span class=\"kwrd\">void<\/span> Clear()\r\n    {\r\n        <span class=\"kwrd\">base<\/span>.Clear();\r\n        <span class=\"kwrd\">foreach<\/span> (var cacheEntry <span class=\"kwrd\">in<\/span> db.PerUserCacheList)\r\n            db.PerUserCacheList.Remove(cacheEntry);\r\n        db.SaveChanges();\r\n    }\r\n\r\n    <span class=\"rem\">\/\/ Notification raised before ADAL accesses the cache.<\/span>\r\n    <span class=\"rem\">\/\/ This is your chance to update the in-memory copy from the DB, if the in-memory version is stale<\/span>\r\n    <span class=\"kwrd\">void<\/span> BeforeAccessNotification(TokenCacheNotificationArgs args)\r\n    {\r\n        <span class=\"kwrd\">if<\/span> (Cache == <span class=\"kwrd\">null<\/span>)\r\n        {\r\n            <span class=\"rem\">\/\/ first time access<\/span>\r\n            Cache = db.PerUserCacheList.FirstOrDefault(c =&gt; c.webUserUniqueId == User);\r\n        }\r\n        <span class=\"kwrd\">else<\/span>\r\n        {   <span class=\"rem\">\/\/ retrieve last write from the DB<\/span>\r\n            var status = from e <span class=\"kwrd\">in<\/span> db.PerUserCacheList\r\n                         <span class=\"kwrd\">where<\/span> (e.webUserUniqueId == User)\r\n                         select <span class=\"kwrd\">new<\/span>\r\n                         {\r\n                             LastWrite = e.LastWrite\r\n                         };\r\n            <span class=\"rem\">\/\/ if the in-memory copy is older than the persistent copy<\/span>\r\n            <span class=\"kwrd\">if<\/span> (status.First().LastWrite &gt; Cache.LastWrite)\r\n            <span class=\"rem\">\/\/\/\/ read from from storage, update in-memory copy<\/span>\r\n            {\r\n                Cache = db.PerUserCacheList.FirstOrDefault(c =&gt; c.webUserUniqueId == User);\r\n            }\r\n        }\r\n        <span class=\"kwrd\">this<\/span>.Deserialize((Cache == <span class=\"kwrd\">null<\/span>) ? <span class=\"kwrd\">null<\/span> : Cache.cacheBits);\r\n    }\r\n    <span class=\"rem\">\/\/ Notification raised after ADAL accessed the cache.<\/span>\r\n    <span class=\"rem\">\/\/ If the HasStateChanged flag is set, ADAL changed the content of the cache<\/span>\r\n    <span class=\"kwrd\">void<\/span> AfterAccessNotification(TokenCacheNotificationArgs args)\r\n    {\r\n        <span class=\"rem\">\/\/ if state changed<\/span>\r\n        <span class=\"kwrd\">if<\/span> (<span class=\"kwrd\">this<\/span>.HasStateChanged)\r\n        {\r\n            Cache = <span class=\"kwrd\">new<\/span> PerWebUserCache\r\n            {\r\n                webUserUniqueId = User,\r\n                cacheBits = <span class=\"kwrd\">this<\/span>.Serialize(),\r\n                LastWrite = DateTime.Now\r\n            };\r\n            <span class=\"rem\">\/\/\/\/ update the DB and the lastwrite                <\/span>\r\n            db.Entry(Cache).State = Cache.EntryId == 0 ? EntityState.Added : EntityState.Modified;                \r\n            db.SaveChanges();\r\n            <span class=\"kwrd\">this<\/span>.HasStateChanged = <span class=\"kwrd\">false<\/span>;\r\n        }\r\n    }\r\n    <span class=\"kwrd\">void<\/span> BeforeWriteNotification(TokenCacheNotificationArgs args)\r\n    {\r\n        <span class=\"rem\">\/\/ if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry<\/span>\r\n    }\r\n}\r\n<\/pre>\n<p>Also in this case, the implementation is pretty self explanatory: I disregarded locks and only added a little timestamp check to avoid swapping potentially sizable blobs from the DB when it\u2019s not necessary.<\/p>\n<h2>Wrap<\/h2>\n<p>I have a confession to make: although I was always bummed by the lack of a viable token caching solution on the server side, and consequent need for complex code for confidential clients, I didn\u2019t believe that a cache redesign would have been possible so late in the cycle given the time constraints we are up against (we want to release soon!!). However the dev team was very passionate about solving that problem, and worked very hard to deliver a design that blew me away \u2013 it satisfied all requirements without affecting schedule! So big kudos to the dev team, especially to Afshin <img decoding=\"async\" class=\"wlEmoticon wlEmoticon-smile\" src=\"https:\/\/www.cloudidentity.com\/blog\/wp-content\/uploads\/2014\/07\/wlEmoticon-smile2.png\" alt=\"Smile\" \/><\/p>\n<p>All the new features discussed here apply to both ADAL .NET and ADAL for Windows Store. However, that does NOT change the defaults: ADAL .NET OOB cache remains in-memory, ADAL for Windows Store retains its default persistent cache. All the news only apply to how you\u2019d implement a custom cache, should you choose to write one. If you need a starting point, you can look at the above snippets. If you want to see them in action, you\u2019ll find them (and possibly others) in our github samples.<\/p>\n<p>Enjoy!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The release candidate of ADAL v2 introduces a new cache model, which makes possible to cache tokens in middle tier apps and dramatically simplifies creating custom caches. In this post I&#8217;ll give you\u00a0 a brief introduction to the new model. You can see the new cache in action in our updates samples. Limitations&#8230;<\/p>\n","protected":false},"author":1,"featured_media":2867,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-2855","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.cloudidentity.com\/blog\/wp-json\/wp\/v2\/posts\/2855","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cloudidentity.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cloudidentity.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cloudidentity.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cloudidentity.com\/blog\/wp-json\/wp\/v2\/comments?post=2855"}],"version-history":[{"count":8,"href":"https:\/\/www.cloudidentity.com\/blog\/wp-json\/wp\/v2\/posts\/2855\/revisions"}],"predecessor-version":[{"id":2877,"href":"https:\/\/www.cloudidentity.com\/blog\/wp-json\/wp\/v2\/posts\/2855\/revisions\/2877"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.cloudidentity.com\/blog\/wp-json\/wp\/v2\/media\/2867"}],"wp:attachment":[{"href":"https:\/\/www.cloudidentity.com\/blog\/wp-json\/wp\/v2\/media?parent=2855"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cloudidentity.com\/blog\/wp-json\/wp\/v2\/categories?post=2855"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cloudidentity.com\/blog\/wp-json\/wp\/v2\/tags?post=2855"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}