{"id":240,"date":"2015-08-06T00:57:04","date_gmt":"2015-08-06T00:57:04","guid":{"rendered":"http:\/\/strawberrycode.com\/blog\/?p=240"},"modified":"2021-05-07T18:11:22","modified_gmt":"2021-05-07T16:11:22","slug":"sfsafariviewcontroller-and-oauth-the-instagram-example","status":"publish","type":"post","link":"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/","title":{"rendered":"SFSafariViewController and OAuth &#8211; the Instagram example"},"content":{"rendered":"\n<p>I think, as a developer, I&#8217;ve never been so excited and frustrated at the same time by the same thing. Sorry for the non-tech reading that, you can stop here, it&#8217;s fine ;) My oh-so-frustrating thing is the brand new <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController <\/code>introduced in WWDC 2015, and especially its interaction with <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">OAuth<\/code>.<\/p>\n\n\n\n<p>If you are reading this article, you have probably watched WWDC&#8217;s session 504: <a href=\"https:\/\/developer.apple.com\/videos\/wwdc\/2015\/?id=504\" target=\"_blank\" rel=\"noopener\">Introduction to Safari View Controller<\/a>. And you\u2019ve probably browsed through a lot of articles arguing the pros and cons of <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code>.<\/p>\n\n\n\n<p>Here, I would like to jump straight to the &#8220;Web-based authentication\u201d section, when <a href=\"https:\/\/twitter.com\/rmondello\" target=\"_blank\" rel=\"noopener\">Ricky Mondello<\/a> showed us the slide below, while saying:<\/p>\n\n\n\n<div style=\"padding-left: 30px;\"><em><em><br>&#8220;Because since Safari View Controller has access to a user&#8217;s credentials, synced across all of their devices with iCloud Keychain, logging in is going to be a breeze.[&#8230;]<\/em><\/em>\n<p>&nbsp;<\/p>\n<p>It takes two steps.<br>The first is where you would&#8217;ve used your own in-app browser, just present an instance of SFSafariViewController.<\/p>\n<p>And once the user is finished logging in and the third-party web service redirects back to your app with the custom URL scheme that you fed it, you can accept that in your <code>AppDelegate<\/code>&#8216;s <code>handleOpenURL<\/code> method.<\/p>\n<p>From there you can inspect the response and dismiss the instance of <code>SFSafariViewController<\/code> because you know that the authentication is done.<\/p>\n<p><em>That&#8217;s it.<br>Two steps.&#8221;<br><\/em><\/p>\n<\/div>\n\n\n\n<p>Source: <a href=\"http:\/\/asciiwwdc.com\/2015\/sessions\/504#t=1558.026\" target=\"_blank\" rel=\"noopener\">http:\/\/asciiwwdc.com\/2015\/sessions\/504#t=1558.026<\/a><\/p>\n\n\n\n<div class=\"wp-block-image size-full wp-image-241\"><figure class=\"aligncenter\"><img loading=\"lazy\" width=\"876\" height=\"545\" src=\"http:\/\/strawberrycode.com\/blog\/wp-content\/uploads\/2015\/07\/Screen-Shot-2015-07-23-at-00.19.07.png\" alt=\"WWDC2015 504 SFSafariViewController\" class=\"wp-image-241\" srcset=\"https:\/\/strawberrycode.com\/blog\/wp-content\/uploads\/2015\/07\/Screen-Shot-2015-07-23-at-00.19.07.png 876w, https:\/\/strawberrycode.com\/blog\/wp-content\/uploads\/2015\/07\/Screen-Shot-2015-07-23-at-00.19.07-300x187.png 300w\" sizes=\"(max-width: 876px) 100vw, 876px\" \/><figcaption>WWDC 2015 &#8211; Session 504 &#8211; SFSafariViewController<\/figcaption><\/figure><\/div>\n\n\n\n<p>Intriguing. Two steps? That sounds nearly too easy. I wanted to see some implementations of it. While searching the web, I found that very interesting article from Rizwan Sattar: <a href=\"https:\/\/library.launchkit.io\/how-ios-9-s-safari-view-controller-could-completely-change-your-app-s-onboarding-experience-2bcf2305137f\" target=\"_blank\" rel=\"noopener\">How iOS 9&#8217;s Safari View Controller could completely change your app\u2019s onboarding experience<\/a>. All of you who have submitted apps to the AppStore know that Apple doesn\u2019t really like the opening of a third-party app in the login workflow. And the \u201cSafari bounce\u201d is not very user-friendly\u2026 So let\u2019s have a look at <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code>, it may solve our dilemma.<\/p>\n\n\n\n<h2>Without further ado, let\u2019s do it!<\/h2>\n\n\n\n<p>Two steps, let&#8217;s see&#8230;<\/p>\n\n\n\n<ol><li>Call the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code> with your favorite login URL<\/li><li>In the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">AppDelegate (application:HandleOpenUrl)<\/code>, when you have parsed the response, dismiss the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code>.<\/li><\/ol>\n\n\n\n<p>That sounds so simple, yet it feels like there a little something missing when you try to implement it. Strangely enough, I couldn\u2019t find any tutorial or example of the implementation of <code><code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">OAuth<\/code><\/code> in <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code>. I searched the web, GitHub, even Twitter: nothing. So I dug into it, and came to a \u201cviable solution\u201d: a communication via Notification between the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">AppDelegate<\/code> and the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">ViewController<\/code>. It is not revolutionary, but it works like a charm (if you have any suggestions, please leave a comment below or on Twitter <a href=\"https:\/\/twitter.com\/cath_schwz\" target=\"_blank\" rel=\"noopener\">@cath_schwz<\/a> :)<\/p>\n\n\n\n<h3>In your ViewController<\/h3>\n\n\n\n<p>Here is the basic setup of your ViewController: import <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SafariServices<\/code>, <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewControllerDelegate<\/code> and its finish method, declare a name (constant) for our notification. And let&#8217;s create an outlet to a button and a <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code> variable.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"swift\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import SafariServices\n\nlet kCloseSafariViewControllerNotification = \"kCloseSafariViewControllerNotification\"\n\nclass ViewController: UIViewController, SFSafariViewControllerDelegate {\n\n    @IBOutlet var loginButton: UIButton!\n    var safariVC: SFSafariViewController?\n    \n    \/\/\n    \/\/ Magic goes here\n    \/\/\n\n\n    \/\/ MARK: - SFSafariViewControllerDelegate\n    \/\/ Called on \"Done\" button\n    func safariViewControllerDidFinish(controller: SFSafariViewController) {\n        controller.dismissViewControllerAnimated(true, completion: nil)\n    }\n}<\/pre>\n\n\n\n<p>Then, let&#8217;s create the method called when you tap on the button: the one that will open the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code> :<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"swift\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ MARK: - Action\n\t@IBAction func loginButtonTapped(sender: AnyObject) {\n\t\tlet authURL = NSURL(string: \"\") \/\/ the URL goes here\n\t\tsafariVC = SFSafariViewController(URL: authURL)\n\t\tsafariVC!.delegate = self\n\t\tself.presentViewController(safariVC!, animated: true, completion: nil)\n\t}<\/pre>\n\n\n\n<p>At that point, if your outlet and action are correctly linked to your view, you should be able to display and dismiss manually the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code>. Don&#8217;t forget to put a URL. Any URL will do for now.<\/p>\n\n\n\n<p>Now let&#8217;s add the magic: in <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">viewDidLoad<\/code>, add an observer that will trigger the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">safariLogin<\/code> method when the notification is sent. It&#8217;s in your <code>safariLogin<\/code> method that you will deal with the OAuth response and &#8220;automatically&#8221; dismiss the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code>.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"swift\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">override func viewDidLoad() {\n        super.viewDidLoad()\n        NSNotificationCenter.defaultCenter().addObserver(self, selector: \"safariLogin:\", name: kCloseSafariViewControllerNotification, object: nil)\n    }\n    \n    func safariLogin(notification: NSNotification) {\n        \/\/ get the url form the auth callback\n        let url = notification.object as! NSURL\n        \/\/ then do whatever you like, for example :\n        \/\/ get the code (token) from the URL\n        \/\/ and do a request to get the information you need (id, name, ...)\n        \/\/ Finally dismiss the Safari View Controller with:\n        self.safariVC!.dismissViewControllerAnimated(true, completion: nil)\n    }\n}<\/pre>\n\n\n\n<h3>In your AppDelegate<\/h3>\n\n\n\n<p>We just saw what happens when the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">ViewController<\/code> receives a notification. Now let&#8217;s see how the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">AppDelegate<\/code> sends it. Easy, no surprises:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"swift\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {\n\n\/\/ just making sure we send the notification when the URL is opened in SFSafariViewController\n    if (sourceApplication == \"com.apple.SafariViewService\") {\n        NSNotificationCenter.defaultCenter().postNotificationName(kCloseSafariViewControllerNotification, object: url)\n        return true\n    }\n    return true\n}<\/pre>\n\n\n\n<h2>What about OAuth?<\/h2>\n\n\n\n<p>We just covered the easiest part: displaying and dismissing the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code>. Now that you have that running, we can add the OAuth component. And to illustrate my example I&#8217;ve chosen Instagram. There is no iOS SDK and no specific way to login via Instagram except by displaying a web view, so it makes the perfect candidate for our study.<\/p>\n\n\n\n<p>I have put the whole <a href=\"https:\/\/github.com\/strawberrycode\/SafariOauthLogin\" target=\"_blank\" rel=\"noopener\">project on GitHub: SafariOauthLogin<\/a>. Don&#8217;t hesitate to fork it and to play with it. Just follow the instructions and it should be working out of the box :)<\/p>\n\n\n\n<h3>Inside the box<\/h3>\n\n\n\n<p>I would like to draw your attention on 2 little things:<\/p>\n\n\n\n<ol><li>The OAuth magic that I&#8217;m using for Instagram comes from <a href=\"https:\/\/github.com\/MoZhouqi\/PhotoBrowser\" target=\"_blank\" rel=\"noopener\">here<\/a>. You will find it in the <code><code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">Auth.<\/code><\/code><code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">swift<\/code> file. It&#8217;s pretty short and clean. I like it.<\/li><li>In the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">safariLogin<\/code> method, I mention a token called &#8220;code&#8221; that comes form the response URL. That &#8220;code&#8221; needs to be extracted in order to be used to &#8220;trade&#8221; actual information from your third-party API. I do so thanks to the method called <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">extractCode<\/code><\/li><\/ol>\n\n\n\n<h2>Further reading<\/h2>\n\n\n\n<p>I would love to have a one size fits all for OAuth logins, but something light enough that it could be kept up to date with the third-parties&#8217; APIs and wouldn&#8217;t break at each and every update. One can dream&#8230;<br>I know there are pretty cool OAuth tools out there, for example: <a href=\"https:\/\/github.com\/dongri\/OAuthSwift\" target=\"_blank\" rel=\"noopener\">OAuthSwift<\/a>, <a href=\"https:\/\/github.com\/p2\/OAuth2\" target=\"_blank\" rel=\"noopener\">OAuth2<\/a>, <a href=\"https:\/\/github.com\/aerogear\/aerogear-ios-oauth2\" target=\"_blank\" rel=\"noopener\">aerogear-ios-oauth2<\/a>. But none of them is using <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code> yet, except <a href=\"https:\/\/github.com\/mwermuth\/OAuthSwift\/tree\/swift2.0\" target=\"_blank\" rel=\"noopener\">mwermuth who has forked OAuthSwift<\/a> to use the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code> on Instagram login too. I will definitely keep an eye on that!<\/p>\n\n\n\n<p>Two more words, bear with me:<br>Reddit has a nice <a href=\"https:\/\/github.com\/reddit\/reddit\/wiki\/OAuth2-iOS-Example\" target=\"_blank\" rel=\"noopener\">OAuth tutorial<\/a>. I believe it can be easily tweaked to use the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">SFSafariViewController<\/code> instead of the <code data-enlighter-language=\"swift\" class=\"EnlighterJSRAW\">web view<\/code>.<br>And finally, if you want a comprehensive <a href=\"http:\/\/www.raywenderlich.com\/99431\/oauth-2-with-swift-tutorial\" target=\"_blank\" rel=\"noopener\">OAuth 2 with Swift tutorial<\/a>, you&#8217;ll be in good hands there. You may want to keep it for an other day though, it&#8217;s quite a long read too!<\/p>\n\n\n\n<p>That&#8217;s all folks! I hope you enjoyed it and that you have found some answers there. Happy coding!<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/strawberrycode\/SafariOauthLogin\" target=\"_blank\" rel=\"noopener\">Find the full project on GitHub: SafariOauthLogin<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I think, as a developer, I&#8217;ve never been so excited and frustrated at the same time by the same thing. Sorry for the non-tech reading that, you can stop here, it&#8217;s fine ;) My oh-so-frustrating thing is the brand new SFSafariViewController introduced in WWDC 2015, and especially its interaction with OAuth. If you are reading this article, you have probably watched WWDC&#8217;s session 504: Introduction to Safari View Controller. And you\u2019ve probably browsed through a lot of articles arguing the pros and cons of SFSafariViewController. Here, I would like to jump straight to the &#8220;Web-based authentication\u201d section, when Ricky Mondello &hellip; <a href=\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">SFSafariViewController and OAuth &#8211; the Instagram example<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":183,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[40],"tags":[42,53,17,55,52,51,50,54,56],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v16.1.1 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>SFSafariViewController and OAuth example - StrawberryCode<\/title>\n<meta name=\"description\" content=\"How to integrate OAuth in the brand new SFSafariViewController introduced in WWDC 2015? No more mysteries with the Instagram login example!\" \/>\n<link rel=\"canonical\" href=\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"SFSafariViewController and OAuth example - StrawberryCode\" \/>\n<meta property=\"og:description\" content=\"How to integrate OAuth in the brand new SFSafariViewController introduced in WWDC 2015? No more mysteries with the Instagram login example!\" \/>\n<meta property=\"og:url\" content=\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/\" \/>\n<meta property=\"og:site_name\" content=\"StrawberryCode\" \/>\n<meta property=\"article:published_time\" content=\"2015-08-06T00:57:04+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2021-05-07T16:11:22+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/strawberrycode.com\/blog\/wp-content\/uploads\/2015\/04\/Apple_Swift_Logo-e1430073805246.png\" \/>\n\t<meta property=\"og:image:width\" content=\"200\" \/>\n\t<meta property=\"og:image:height\" content=\"200\" \/>\n<meta name=\"twitter:label1\" content=\"Estimated reading time\">\n\t<meta name=\"twitter:data1\" content=\"6 minutes\">\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/strawberrycode.com\/blog\/#website\",\"url\":\"https:\/\/strawberrycode.com\/blog\/\",\"name\":\"StrawberryCode\",\"description\":\"Fruit for Thought\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/strawberrycode.com\/blog\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-GB\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/#primaryimage\",\"inLanguage\":\"en-GB\",\"url\":\"https:\/\/strawberrycode.com\/blog\/wp-content\/uploads\/2015\/04\/Apple_Swift_Logo-e1430073805246.png\",\"contentUrl\":\"https:\/\/strawberrycode.com\/blog\/wp-content\/uploads\/2015\/04\/Apple_Swift_Logo-e1430073805246.png\",\"width\":200,\"height\":200,\"caption\":\"Apple Swift Logo\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/#webpage\",\"url\":\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/\",\"name\":\"SFSafariViewController and OAuth example - StrawberryCode\",\"isPartOf\":{\"@id\":\"https:\/\/strawberrycode.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/#primaryimage\"},\"datePublished\":\"2015-08-06T00:57:04+00:00\",\"dateModified\":\"2021-05-07T16:11:22+00:00\",\"author\":{\"@id\":\"https:\/\/strawberrycode.com\/blog\/#\/schema\/person\/c328d959959928f47281d7a0ec779e2a\"},\"description\":\"How to integrate OAuth in the brand new SFSafariViewController introduced in WWDC 2015? No more mysteries with the Instagram login example!\",\"breadcrumb\":{\"@id\":\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/#breadcrumb\"},\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"item\":{\"@type\":\"WebPage\",\"@id\":\"https:\/\/strawberrycode.com\/blog\/\",\"url\":\"https:\/\/strawberrycode.com\/blog\/\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"position\":2,\"item\":{\"@type\":\"WebPage\",\"@id\":\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/\",\"url\":\"https:\/\/strawberrycode.com\/blog\/sfsafariviewcontroller-and-oauth-the-instagram-example\/\",\"name\":\"SFSafariViewController and OAuth &#8211; the Instagram example\"}}]},{\"@type\":\"Person\",\"@id\":\"https:\/\/strawberrycode.com\/blog\/#\/schema\/person\/c328d959959928f47281d7a0ec779e2a\",\"name\":\"StrawberryCode\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/strawberrycode.com\/blog\/wp-json\/wp\/v2\/posts\/240"}],"collection":[{"href":"https:\/\/strawberrycode.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/strawberrycode.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/strawberrycode.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/strawberrycode.com\/blog\/wp-json\/wp\/v2\/comments?post=240"}],"version-history":[{"count":41,"href":"https:\/\/strawberrycode.com\/blog\/wp-json\/wp\/v2\/posts\/240\/revisions"}],"predecessor-version":[{"id":590,"href":"https:\/\/strawberrycode.com\/blog\/wp-json\/wp\/v2\/posts\/240\/revisions\/590"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/strawberrycode.com\/blog\/wp-json\/wp\/v2\/media\/183"}],"wp:attachment":[{"href":"https:\/\/strawberrycode.com\/blog\/wp-json\/wp\/v2\/media?parent=240"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/strawberrycode.com\/blog\/wp-json\/wp\/v2\/categories?post=240"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/strawberrycode.com\/blog\/wp-json\/wp\/v2\/tags?post=240"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}