{"id":192,"date":"2024-12-27T10:14:39","date_gmt":"2024-12-27T10:14:39","guid":{"rendered":"https:\/\/ranjeshviswa.com\/?p=192"},"modified":"2024-12-27T10:16:23","modified_gmt":"2024-12-27T10:16:23","slug":"how-to-create-an-oauth2-client-using-spring-boot","status":"publish","type":"post","link":"https:\/\/ranjeshviswa.com\/?p=192","title":{"rendered":"How to create an OAuth2 Client using Spring Boot"},"content":{"rendered":"<h2><span style=\"font-weight: 400;\">Overview<\/span><\/h2>\n<p>&nbsp;<\/p>\n<p>This guide shows how to create an OAuth2 client and consume an endpoint protected by OAuth2 authorization server using Spring Boot.<\/p>\n<h2 id=\"a3f1\" class=\"mi mj fq bf mk ml mm mn mo mp mq mr ms lv mt mu mv lz mw mx my md mz na nb nc bk\" data-selectable-paragraph=\"\">Introduction<\/h2>\n<p>Let\u2019s say you are part of a Bank backend development team and you need to create an internal endpoint, which will give real time status of international payment transaction. The internal endpoint when invoked will call an external swift endpoint, which gives detailed status history of payment went through swift network. The response returned from swift needs to be transformed into customised simple status response. The swift endpoint is protected by OAuth2 Authorization server using grant type\u00a0<a class=\"af ni\" href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc7523\" target=\"_blank\" rel=\"noopener ugc nofollow noreferrer\">urn:ietf:params:oauth:grant-type:jwt-bearer<\/a>. This grant type enables an OAuth client to request an access token using JSON Web Tokens (<a class=\"af ni\" href=\"https:\/\/jwt.io\/\" target=\"_blank\" rel=\"noopener ugc nofollow noreferrer\">JWT<\/a>). The subsequent part of this guide will show key components involved in creating a Spring boot application, which exposes a rest end point and also acts as an OAuth2 Client to external Swift server.<\/p>\n<h2 id=\"1443\" class=\"mi mj fq bf mk ml mm mn mo mp mq mr ms lv mt mu mv lz mw mx my md mz na nb nc bk\" data-selectable-paragraph=\"\">Proposed System Architecture<\/h2>\n<p>&nbsp;<\/p>\n<figure class=\"nm nn no np nq nr nj nk paragraph-image\">\n<div class=\"ns nt ed nu bh nv\" tabindex=\"0\" role=\"button\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-193\" src=\"https:\/\/ranjeshviswa.com\/wp-content\/uploads\/2024\/12\/OAuth2ClientDemo2.drawio.png\" alt=\"\" width=\"841\" height=\"360\" \/><\/div>\n<\/figure>\n<div tabindex=\"0\" role=\"button\">\n<h2 id=\"ab3c\" class=\"mi mj fq bf mk ml mm mn mo mp mq mr ms lv mt mu mv lz mw mx my md mz na nb nc bk\" data-selectable-paragraph=\"\">Rest Controller<\/h2>\n<pre class=\"nm nn no np nq nx ny nz bp oa bb bk\"><span class=\"hljs-meta\">@RestController<\/span>\r\n<span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title.class\">TransactionController<\/span>(<span class=\"hljs-keyword\">val<\/span> swiftTransactionService: SwiftTransactionService) {\r\n<span class=\"hljs-meta\">@GetMapping(<span class=\"hljs-string\">\"\/transactions\/{uetr}\"<\/span>)<\/span>\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">  fun<\/span> <span class=\"hljs-title\">getTransactionStatus<\/span><span class=\"hljs-params\">(<span class=\"hljs-meta\">@PathVariable<\/span> uetr: <span class=\"hljs-type\">String<\/span>)<\/span><\/span>: ResponseEntity&lt;TransactionStatus&gt; {\r\n<span class=\"hljs-keyword\">    val<\/span> status = swiftTransactionService.getTransactionStatus(uetr)\r\n<span class=\"hljs-keyword\">    return<\/span> ResponseEntity.ok(TransactionStatus(status))\r\n  }\r\n}<\/pre>\n<p>Typical Spring Boot Rest controller to create an endpoint , it uses swiftTransactionService which in turns call external swift resource end point, protected by Swift Authorization server<\/p>\n<h2 id=\"a8c4\" class=\"mi mj fq bf mk ml mm mn mo mp mq mr ms lv mt mu mv lz mw mx my md mz na nb nc bk\" data-selectable-paragraph=\"\">Service<\/h2>\n<pre class=\"nm nn no np nq nx ny nz bp oa bb bk\"><span class=\"hljs-meta\">@Service<\/span>\r\n<span class=\"hljs-meta\">@EnableConfigurationProperties(SwiftApiProperties::class)<\/span>\r\n<span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title.class\">SwiftTransactionService<\/span>(\r\n<span class=\"hljs-keyword\">  private<\/span> <span class=\"hljs-keyword\">val<\/span> webClient: WebClient,\r\n<span class=\"hljs-keyword\">  private<\/span> <span class=\"hljs-keyword\">val<\/span> swiftApiProperties: SwiftApiProperties\r\n) {\r\n\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">  fun<\/span> <span class=\"hljs-title\">getTransactionStatus<\/span><span class=\"hljs-params\">(uetr: <span class=\"hljs-type\">String<\/span>)<\/span><\/span>: String {\r\n<span class=\"hljs-keyword\">  val<\/span> swiftResponse = webClient\r\n    .<span class=\"hljs-keyword\">get<\/span>()\r\n    .uri(URI(swiftApiProperties.resourceUrl.format(uetr)))\r\n    .retrieve()\r\n    .bodyToMono&lt;Map&lt;String, Any&gt;&gt;()\r\n    .block() ?: emptyMap()\r\n<span class=\"hljs-keyword\">  val<\/span> status = swiftResponse[<span class=\"hljs-string\">\"transaction_status\"<\/span>] <span class=\"hljs-keyword\">as<\/span> String\r\n<span class=\"hljs-keyword\">  return<\/span> status\r\n  }\r\n}<\/pre>\n<p id=\"7293\" class=\"pw-post-body-paragraph lm ln fq lo b go lp lq lr gr ls lt lu lv lw lx ly lz ma mb mc md me mf mg mh fj bk\" data-selectable-paragraph=\"\">Service layer uses WebClient to call external resource end point\u00a0<a class=\"af ni\" href=\"https:\/\/sandbox.swift.com\/swift-apitracker\/v5\/payments\/%7Buetr%7D\/transactions\" target=\"_blank\" rel=\"noopener ugc nofollow noreferrer\">https:\/\/sandbox.swift.com\/swift-apitracker\/v5\/payments\/{uetr}\/transactions<\/a>, which is protected by authorization server. WebClient is configured to automtically send client credentials like clientId, clientSecret, JWT to Authorization server to get valid access token, which is then used to access resource end point.<\/p>\n<h2 id=\"2961\" class=\"mi mj fq bf mk ml mm mn mo mp mq mr ms lv mt mu mv lz mw mx my md mz na nb nc bk\" data-selectable-paragraph=\"\">JwtTokenUtil<\/h2>\n<pre><span class=\"hljs-meta\">@Component<\/span>\r\n<span class=\"hljs-meta\">@EnableConfigurationProperties(SwiftApiProperties::class)<\/span>\r\n<span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title.class\">JwtTokenUtil<\/span>(<span class=\"hljs-keyword\">val<\/span> swiftApiProperties: SwiftApiProperties) {\r\n\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">  fun<\/span> <span class=\"hljs-title\">createJwtToken<\/span><span class=\"hljs-params\">()<\/span><\/span>: String {\r\n<span class=\"hljs-keyword\">    val<\/span> certFactory = CertificateFactory.getInstance(<span class=\"hljs-string\">\"X.509\"<\/span>)\r\n<span class=\"hljs-keyword\">    val<\/span> certificateInputStream = loadPublicCertificateAsStream(swiftApiProperties.certPath)\r\n<span class=\"hljs-keyword\">    val<\/span> certificate: X509Certificate = certFactory.generateCertificate(certificateInputStream) <span class=\"hljs-keyword\">as<\/span> X509Certificate\r\n<span class=\"hljs-keyword\">    val<\/span> base64EncodedCert = com.nimbusds.jose.util.Base64.encode(certificate.encoded)\r\n<span class=\"hljs-keyword\">    val<\/span> header = JWSHeader.Builder(JWSAlgorithm.RS256)\r\n     .type(JOSEObjectType.JWT)\r\n     .x509CertChain(listOf(base64EncodedCert))\r\n     .build()\r\n\r\n<span class=\"hljs-keyword\">    val<\/span> jti = (<span class=\"hljs-number\">1.<\/span><span class=\"hljs-number\">.12<\/span>)\r\n      .map { <span class=\"hljs-string\">\"abcdefghijklmnopqrstuvwxyz0123456789\"<\/span>.random() }\r\n      .joinToString(<span class=\"hljs-string\">\"\"<\/span>)\r\n<span class=\"hljs-keyword\">    val<\/span> currentTimeMillis = System.currentTimeMillis()\r\n<span class=\"hljs-keyword\">    val<\/span> issuedAt = currentTimeMillis \/ <span class=\"hljs-number\">1000<\/span>\r\n<span class=\"hljs-keyword\">    val<\/span> expiration = issuedAt + <span class=\"hljs-number\">900<\/span> <span class=\"hljs-comment\">\/\/ 15 minutes from issuance<\/span>\r\n<span class=\"hljs-keyword\">    val<\/span> claims = JWTClaimsSet.Builder()\r\n      .issuer(swiftApiProperties.issuer)\r\n      .audience(swiftApiProperties.audience)\r\n      .subject(swiftApiProperties.subject)\r\n      .jwtID(jti)\r\n      .expirationTime(Date(expiration * <span class=\"hljs-number\">1000<\/span>))\r\n      .issueTime(Date(issuedAt * <span class=\"hljs-number\">1000<\/span>))\r\n      .build()\r\n\r\n<span class=\"hljs-keyword\">    val<\/span> signedJWT = SignedJWT(\r\n      header,\r\n      claims\r\n      )\r\n<span class=\"hljs-keyword\">    val<\/span> privateKey = loadPrivateKey(swiftApiProperties.privateKeyPath)\r\n      signedJWT.sign(RSASSASigner(privateKey))\r\n<span class=\"hljs-keyword\">      return<\/span> signedJWT.serialize()\r\n  }\r\n\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">  fun<\/span> <span class=\"hljs-title\">loadPrivateKey<\/span><span class=\"hljs-params\">(privateKeyPath: <span class=\"hljs-type\">String<\/span>)<\/span><\/span>: PrivateKey {\r\n    Security.addProvider(BouncyCastleProvider())\r\n<span class=\"hljs-comment\">    \/\/ Load the private key from the PEM file<\/span>\r\n<span class=\"hljs-keyword\">    val<\/span> resource = ClassPathResource(privateKeyPath)\r\n<span class=\"hljs-keyword\">    val<\/span> keyContentPKCS1 = resource.inputStream.bufferedReader(StandardCharsets.UTF_8).use { it.readText() }\r\n     .replace(<span class=\"hljs-string\">\"-----BEGIN RSA PRIVATE KEY-----\"<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\r\n     .replace(<span class=\"hljs-string\">\"-----END RSA PRIVATE KEY-----\"<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\r\n     .replace(<span class=\"hljs-string\">\"\\\\s+\"<\/span>.toRegex(), <span class=\"hljs-string\">\"\"<\/span>)\r\n\r\n<span class=\"hljs-keyword\">    val<\/span> keyBytes: ByteArray = Base64.getDecoder().decode(keyContentPKCS1)\r\n<span class=\"hljs-keyword\">    val<\/span> asn1Parser = ASN1StreamParser(keyBytes)\r\n<span class=\"hljs-keyword\">    val<\/span> asn1Object = asn1Parser.readObject()\r\n<span class=\"hljs-keyword\">    val<\/span> rsaKey = RSAPrivateKey.getInstance(asn1Object.toASN1Primitive())\r\n<span class=\"hljs-keyword\">    val<\/span> keySpec = RSAPrivateCrtKeySpec(\r\n     rsaKey.modulus,\r\n     rsaKey.publicExponent,\r\n     rsaKey.privateExponent,\r\n     rsaKey.prime1,\r\n     rsaKey.prime2,\r\n     rsaKey.exponent1,\r\n     rsaKey.exponent2,\r\n     rsaKey.coefficient\r\n     )\r\n<span class=\"hljs-keyword\">  val<\/span> keyFactory = KeyFactory.getInstance(<span class=\"hljs-string\">\"RSA\"<\/span>)\r\n<span class=\"hljs-keyword\">  val<\/span> privateKey = keyFactory.generatePrivate(keySpec)\r\n<span class=\"hljs-keyword\">  return<\/span> privateKey\r\n}\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">  fun<\/span> <span class=\"hljs-title\">loadPublicCertificateAsStream<\/span><span class=\"hljs-params\">(filePath: <span class=\"hljs-type\">String<\/span>)<\/span><\/span>: InputStream {\r\n<span class=\"hljs-keyword\">    val<\/span> resource = ClassPathResource(filePath)\r\n<span class=\"hljs-keyword\">    return<\/span> resource.inputStream\r\n  }\r\n}<\/pre>\n<p id=\"60db\" class=\"pw-post-body-paragraph lm ln fq lo b go lp lq lr gr ls lt lu lv lw lx ly lz ma mb mc md me mf mg mh fj bk\" data-selectable-paragraph=\"\">This utlity is used to create JWT dynamically. JWT header is constructed using clients public certificate, kept as cert.pem in resources folder.JWT payload is constructed using statically configured: issuer, audience, subject, and dynamically generated: JTI, issued Time and expiration Time. The consolidated JWT is then signed with clients private Key , kept as privateKey.pem in resources folder.<\/p>\n<p id=\"f23c\" class=\"pw-post-body-paragraph lm ln fq lo b go lp lq lr gr ls lt lu lv lw lx ly lz ma mb mc md me mf mg mh fj bk\" data-selectable-paragraph=\"\">P.S. Sensitive information like private Key and public certificate should not be kept along side the code for security reason. Ideally they should be kept in secure vault like AWS secret manager and access dynamically. For the simplicity of this demo, it is kept along side the code.<\/p>\n<h2 id=\"37e6\" class=\"mi mj fq bf mk ml mm mn mo mp mq mr ms lv mt mu mv lz mw mx my md mz na nb nc bk\" data-selectable-paragraph=\"\">OAuth2ClientConfig<\/h2>\n<pre><span class=\"hljs-meta\">@Configuration<\/span>\r\n<span class=\"hljs-meta\">@EnableConfigurationProperties(SwiftApiProperties::class)<\/span>\r\n<span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title.class\">OAuth2ClientConfig<\/span>(\r\n<span class=\"hljs-keyword\">  val<\/span> clientRegistrationRepository: ClientRegistrationRepository,\r\n<span class=\"hljs-keyword\">  val<\/span> authorizedClientRepository: OAuth2AuthorizedClientRepository,\r\n<span class=\"hljs-keyword\">  val<\/span> jwtTokenUtil: JwtTokenUtil,\r\n<span class=\"hljs-keyword\">  val<\/span> swiftApiProperties: SwiftApiProperties\r\n) {\r\n<span class=\"hljs-meta\">  @Bean<\/span>\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">  fun<\/span> <span class=\"hljs-title\">authorizedClientManager<\/span><span class=\"hljs-params\">()<\/span><\/span>: OAuth2AuthorizedClientManager {\r\n<span class=\"hljs-keyword\">    val<\/span> authorizedClientManager = DefaultOAuth2AuthorizedClientManager(\r\n      clientRegistrationRepository,\r\n      authorizedClientRepository\r\n     )\r\n    authorizedClientManager.setAuthorizedClientProvider { context -&gt;\r\n<span class=\"hljs-keyword\">      val<\/span> clientRegistration = context.clientRegistration\r\n<span class=\"hljs-keyword\">      val<\/span> jwtBearerToken = jwtTokenUtil.createJwtToken()\r\n<span class=\"hljs-keyword\">      val<\/span> formData: MultiValueMap&lt;String, String&gt; = LinkedMultiValueMap()\r\n      formData.add(<span class=\"hljs-string\">\"grant_type\"<\/span>, swiftApiProperties.grantType)\r\n      formData.add(<span class=\"hljs-string\">\"assertion\"<\/span>, jwtBearerToken)\r\n      formData.add(<span class=\"hljs-string\">\"scope\"<\/span>, swiftApiProperties.scope)\r\n<span class=\"hljs-keyword\">      val<\/span> body = BodyInserters\r\n       .fromFormData(formData)\r\n      WebClient.builder()\r\n       .defaultHeader(<span class=\"hljs-string\">\"Content-Type\"<\/span>, <span class=\"hljs-string\">\"application\/x-www-form-urlencoded\"<\/span>)\r\n       .defaultHeaders { headers -&gt;\r\n          headers.setBasicAuth(\r\n            swiftApiProperties.consumerKey,\r\n            swiftApiProperties.consumerSecret\r\n          )\r\n        }\r\n        .build()\r\n        .post()\r\n        .uri(URI(clientRegistration.providerDetails.tokenUri))\r\n        .body(body)\r\n        .retrieve()\r\n        .bodyToMono&lt;Map&lt;String, Any&gt;&gt;()\r\n        .map { responseMap -&gt;\r\n          OAuth2AuthorizedClient(\r\n            clientRegistration, context.principal.name, OAuth2AccessToken(\r\n            OAuth2AccessToken.TokenType.BEARER,\r\n            responseMap?.let {\r\n              it[<span class=\"hljs-string\">\"access_token\"<\/span>] <span class=\"hljs-keyword\">as<\/span> String\r\n            },\r\n            Instant.now(),\r\n            Instant.now().plusSeconds(<span class=\"hljs-number\">1799<\/span>)\r\n          )\r\n          )\r\n        }\r\n        .block()\r\n\r\n    }\r\n<span class=\"hljs-keyword\">    return<\/span> authorizedClientManager\r\n  }\r\n<span class=\"hljs-meta\">  @Bean<\/span>\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">  fun<\/span> <span class=\"hljs-title\">webClient<\/span><span class=\"hljs-params\">(authorizedClientManager: <span class=\"hljs-type\">OAuth2AuthorizedClientManager<\/span>)<\/span><\/span>: WebClient {\r\n<span class=\"hljs-keyword\">    val<\/span> oauth2Filter = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)\r\n    oauth2Filter.setDefaultClientRegistrationId(<span class=\"hljs-string\">\"swift-oauth-client\"<\/span>)\r\n<span class=\"hljs-keyword\">    return<\/span> WebClient.builder()\r\n      .apply(oauth2Filter.oauth2Configuration())\r\n      .build()\r\n  }\r\n\r\n}<\/pre>\n<p id=\"637c\" class=\"pw-post-body-paragraph lm ln fq lo b go lp lq lr gr ls lt lu lv lw lx ly lz ma mb mc md me mf mg mh fj bk\" data-selectable-paragraph=\"\">Spring Bean OAuth2AuthorizedClientManager is configured to call Swift token endpoint automatically and get access token, when ever new token is required or when old token is expired. The bean is injected into Spring Bean WebClient and is subsequently used in Service layer to call swift resource endpoint<\/p>\n<h2 id=\"4f51\" class=\"mi mj fq bf mk ml mm mn mo mp mq mr ms lv mt mu mv lz mw mx my md mz na nb nc bk\" data-selectable-paragraph=\"\">Summary<\/h2>\n<ul class=\"\">\n<li id=\"5344\" class=\"lm ln fq lo b go nd lq lr gr ne lt lu lv nf lx ly lz ng mb mc md nh mf mg mh og oh oi bk\" data-selectable-paragraph=\"\">Implementing OAuth2 Client with Spring Boot and WebClient reduces complexity and boilerplate code.<\/li>\n<li id=\"6dd0\" class=\"lm ln fq lo b go oj lq lr gr ok lt lu lv ol lx ly lz om mb mc md on mf mg mh og oh oi bk\" data-selectable-paragraph=\"\">Bean type OAuth2AuthorizedClientManager automates retrieval of access token required for accessing protected endpoint and hides low level plumbing<\/li>\n<li id=\"9044\" class=\"lm ln fq lo b go oj lq lr gr ok lt lu lv ol lx ly lz om mb mc md on mf mg mh og oh oi bk\" data-selectable-paragraph=\"\">Code can be accessed from\u00a0<a class=\"af ni\" href=\"https:\/\/github.com\/ranjesh1\/oauth2-client-demo\" target=\"_blank\" rel=\"noopener ugc nofollow noreferrer\">here<\/a><\/li>\n<li id=\"441a\" class=\"lm ln fq lo b go oj lq lr gr ok lt lu lv ol lx ly lz om mb mc md on mf mg mh og oh oi bk\" data-selectable-paragraph=\"\">Useful references:<\/li>\n<\/ul>\n<p id=\"2931\" class=\"pw-post-body-paragraph lm ln fq lo b go lp lq lr gr ls lt lu lv lw lx ly lz ma mb mc md me mf mg mh fj bk\" data-selectable-paragraph=\"\"><a class=\"af ni\" href=\"https:\/\/oauth.net\/2\/\" target=\"_blank\" rel=\"noopener ugc nofollow noreferrer\">https:\/\/oauth.net\/2\/<\/a><\/p>\n<p id=\"8a5e\" class=\"pw-post-body-paragraph lm ln fq lo b go lp lq lr gr ls lt lu lv lw lx ly lz ma mb mc md me mf mg mh fj bk\" data-selectable-paragraph=\"\"><a class=\"af ni\" href=\"https:\/\/auth0.com\/intro-to-iam\/what-is-oauth-2\" target=\"_blank\" rel=\"noopener ugc nofollow noreferrer\">https:\/\/auth0.com\/intro-to-iam\/what-is-oauth-2<\/a><\/p>\n<p id=\"75a5\" class=\"pw-post-body-paragraph lm ln fq lo b go lp lq lr gr ls lt lu lv lw lx ly lz ma mb mc md me mf mg mh fj bk\" data-selectable-paragraph=\"\"><a class=\"af ni\" href=\"https:\/\/en.wikipedia.org\/wiki\/JSON_Web_Token\" target=\"_blank\" rel=\"noopener ugc nofollow noreferrer\">https:\/\/en.wikipedia.org\/wiki\/JSON_Web_Token<\/a><\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Overview &nbsp; This guide shows how to create an OAuth2 client and consume an endpoint protected by OAuth2 authorization server using Spring Boot. Introduction Let\u2019s say you are part of a Bank backend development team and you need to create an internal endpoint, which will give real time status of international payment transaction. The internal &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/ranjeshviswa.com\/?p=192\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;How to create an OAuth2 Client using Spring Boot&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":195,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[2,13,27,8,4,1],"tags":[],"_links":{"self":[{"href":"https:\/\/ranjeshviswa.com\/index.php?rest_route=\/wp\/v2\/posts\/192"}],"collection":[{"href":"https:\/\/ranjeshviswa.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ranjeshviswa.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ranjeshviswa.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ranjeshviswa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=192"}],"version-history":[{"count":4,"href":"https:\/\/ranjeshviswa.com\/index.php?rest_route=\/wp\/v2\/posts\/192\/revisions"}],"predecessor-version":[{"id":198,"href":"https:\/\/ranjeshviswa.com\/index.php?rest_route=\/wp\/v2\/posts\/192\/revisions\/198"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ranjeshviswa.com\/index.php?rest_route=\/wp\/v2\/media\/195"}],"wp:attachment":[{"href":"https:\/\/ranjeshviswa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=192"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ranjeshviswa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=192"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ranjeshviswa.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=192"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}