{"id":3381,"date":"2026-04-06T21:51:24","date_gmt":"2026-04-07T01:51:24","guid":{"rendered":"https:\/\/blog.lufamily.ca\/kang\/?p=3381"},"modified":"2026-04-06T21:51:28","modified_gmt":"2026-04-07T01:51:28","slug":"webauthn-with-email-implementation","status":"publish","type":"post","link":"https:\/\/blog.lufamily.ca\/kang\/2026\/04\/06\/webauthn-with-email-implementation\/","title":{"rendered":"WebAuthn with Email Implementation"},"content":{"rendered":"\n<p>Over the past few years I have developed several services that can be accessed using a web site. Many if not all of these sites require authentication. In the past I have typically adopted a typical user id and password technique, and more recently an email based authentication along with the user&#8217;s external IP address, so that they do not need to be burdened with remembering the password.<\/p>\n\n\n\n<p>When my iPhone started to adopt the <a href=\"https:\/\/webauthn.io\" target=\"_blank\" rel=\"noreferrer noopener\">WebAuthn<\/a> passkey solutions I wanted to make use of this convenient solution for my sites as well. As you can see from the chart below, the adoption across the different platforms and devices are now universal.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/developers.yubico.com\/Developer_Program\/WebAuthn_Starter_Kit\/Browser_Support_Matrix.html\" target=\"_blank\" rel=\" noreferrer noopener\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"363\" src=\"https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-8.42.56-PM-1024x363.png\" alt=\"\" class=\"wp-image-3382\" srcset=\"https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-8.42.56-PM-1024x363.png 1024w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-8.42.56-PM-300x106.png 300w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-8.42.56-PM-768x272.png 768w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-8.42.56-PM-1200x425.png 1200w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-8.42.56-PM.png 1502w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/a><figcaption class=\"wp-element-caption\">Compatibility List<\/figcaption><\/figure>\n\n\n\n<p>I went about to develop my own identity provider server using the <a href=\"https:\/\/pypi.org\/project\/webauthn\/\" target=\"_blank\" rel=\"noreferrer noopener\">python WebAuthn package<\/a>. Why did I develop my own solution and instead of using one of the open source solution? I wanted to learn how this works, and what better way to do it than implementing my own version. I also wanted to customize it based on a list of authorized email with the ability to track and manage the access.<\/p>\n\n\n\n<p>This was also the first solution where I used AI to help me vibe code the browser side of the solution. It used the <code>navigator.credentials<\/code> object to do most of the heavy lifting. The AI generated code at the time is fraught with many errors and bad assumptions which I had fix manually. This was more than a year ago, so I am sure things have improved by now.<\/p>\n\n\n\n<p>In the end, I deployed this custom identity service on <a href=\"https:\/\/auth.lufamily.ca\" target=\"_blank\" rel=\"noreferrer noopener\">auth.lufamily.ca<\/a>. This custom service also handled the email authentication flow, which goes something like this:<\/p>\n\n\n\n<div class=\"wp-block-boldblocks-svg-block svg-b-1 sm-svg-justify-alignment\"><div class=\"wp-block-boldblocks-svg-block__inner sm-svg-width\"><svg version=\"1.1\" width=\"281px\" height=\"321px\" viewBox=\"0 0 281 350\" id=\"svg40\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n  \n  <defs id=\"defs1\"><\/defs>\n  <rect fill=\"#ffffff\" width=\"100%\" height=\"100%\" x=\"0\" y=\"0\" style=\"fill: light-dark(#ffffff, var(--ge-dark-color, #121212));\" id=\"rect1\"><\/rect>\n  <text x=\"220\" y=\"224\" fill=\"light-dark(#000000, #ffffff)\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\" id=\"text24-5\" style=\"white-space:pre;inline-size:105.463;display:inline\" transform=\"translate(-81.947177,-107.49829)\" xml:space=\"preserve\"><tspan x=\"220\" y=\"224\" id=\"tspan7\">No<\/tspan><\/text>\n  <text x=\"220\" y=\"224\" fill=\"light-dark(#000000, #ffffff)\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\" id=\"text24-5-0\" style=\"white-space:pre;inline-size:105.463;display:inline\" transform=\"translate(26.605977,-58.809057)\" xml:space=\"preserve\"><tspan x=\"220\" y=\"224\" id=\"tspan8\">Email\n<\/tspan><tspan x=\"220\" y=\"239.00001\" id=\"tspan9\">Sent<\/tspan><\/text>\n  <text x=\"220\" y=\"224\" fill=\"light-dark(#000000, #ffffff)\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\" id=\"text24-5-1\" style=\"white-space:pre;inline-size:105.463;display:inline\" transform=\"translate(-146.22469,-46.234686)\" xml:space=\"preserve\"><tspan x=\"220\" y=\"224\" id=\"tspan10\">Yes<\/tspan><\/text>\n  <g data-cell-id=\"1\" id=\"g38\">\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-12\" id=\"g3\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g2\">\n        <path d=\"M 60,40 V 73.63\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path1\"><\/path>\n        <path d=\"m 60,78.88 -3.5,-7 3.5,1.75 3.5,-1.75 z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path2\"><\/path>\n      <\/g>\n    <\/g>\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-2\" id=\"g7\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g4\">\n        <rect x=\"0\" y=\"0\" width=\"120\" height=\"40\" rx=\"20\" ry=\"20\" fill=\"#ffffff\" stroke=\"#000000\" id=\"rect3\"><\/rect>\n      <\/g>\n      <g id=\"g6\">\n        <g id=\"g5\">\n          <switch id=\"switch4\">\n            \n            <text x=\"60\" y=\"24\" fill=\"light-dark(#000000, #ffffff)\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\" id=\"text4\">Goto Site<\/text>\n          <\/switch>\n        <\/g>\n      <\/g>\n    <\/g>\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-13\" id=\"g9\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g8\">\n        <path d=\"m 120,120 h 33.63\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path7\"><\/path>\n        <path d=\"m 158.88,120 -7,3.5 1.75,-3.5 -1.75,-3.5 z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path8\"><\/path>\n      <\/g>\n    <\/g>\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-14\" id=\"g11\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g10\">\n        <path d=\"m 60,160 v 33.63\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path9\"><\/path>\n        <path d=\"m 60,198.88 -3.5,-7 3.5,1.75 3.5,-1.75 z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path10\"><\/path>\n      <\/g>\n    <\/g>\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-5\" id=\"g15\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g12\">\n        <path d=\"M 60,80 120,120 60,160 0,120 Z\" fill=\"#ffffff\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path11\"><\/path>\n      <\/g>\n      <g id=\"g14\">\n        <g id=\"g13\">\n          <switch id=\"switch12\">\n            \n            <text x=\"60\" y=\"124\" fill=\"light-dark(#000000, #ffffff)\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\" id=\"text12\" style=\"white-space:pre;inline-size:90.3413;display:inline\" transform=\"translate(0,-10)\" xml:space=\"preserve\"><tspan x=\"60\" y=\"124\" id=\"tspan11\">Already<tspan y=\"124\" id=\"tspan12\"> <\/tspan><\/tspan><tspan x=\"60\" y=\"139\" id=\"tspan13\">Registered?<\/tspan><\/text>\n          <\/switch>\n        <\/g>\n      <\/g>\n    <\/g>\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-15\" id=\"g17\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g16\">\n        <path d=\"m 220,140 v 53.63\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path15\"><\/path>\n        <path d=\"m 220,198.88 -3.5,-7 3.5,1.75 3.5,-1.75 z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path16\"><\/path>\n      <\/g>\n    <\/g>\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-6\" id=\"g21\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g18\">\n        <rect x=\"160\" y=\"100\" width=\"120\" height=\"40\" fill=\"#ffffff\" stroke=\"#000000\" id=\"rect17\"><\/rect>\n      <\/g>\n      <g id=\"g20\">\n        <g id=\"g19\">\n          <switch id=\"switch18\">\n            \n            <text x=\"220\" y=\"124\" fill=\"light-dark(#000000, #ffffff)\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\" id=\"text18\">Register with Email<\/text>\n          <\/switch>\n        <\/g>\n      <\/g>\n    <\/g>\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-17\" id=\"g23\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g22\">\n        <path d=\"m 220,240 v 60 h -13.63\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path21\"><\/path>\n        <path d=\"m 201.12,300 7,-3.5 -1.75,3.5 1.75,3.5 z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path22\"><\/path>\n      <\/g>\n    <\/g>\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-7\" id=\"g27\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g24\">\n        <rect x=\"160\" y=\"200\" width=\"120\" height=\"40\" fill=\"#ffffff\" stroke=\"#000000\" id=\"rect23\"><\/rect>\n      <\/g>\n      <g id=\"g26\">\n        <g id=\"g25\">\n          <switch id=\"switch24\">\n            \n            <text x=\"220\" y=\"224\" fill=\"light-dark(#000000, #ffffff)\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\" id=\"text24\" style=\"white-space:pre;inline-size:105.463;display:inline\" transform=\"translate(0,-7)\" xml:space=\"preserve\"><tspan x=\"220\" y=\"224\" id=\"tspan14\">Read Email and<tspan y=\"224\" id=\"tspan15\"> <\/tspan><\/tspan><tspan x=\"220\" y=\"239.00001\" id=\"tspan16\">Click on Welcome<\/tspan><\/text>\n          <\/switch>\n        <\/g>\n      <\/g>\n    <\/g>\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-16\" id=\"g29\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g28\">\n        <path d=\"m 60,240 v 60 h 13.63\" fill=\"none\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path27\"><\/path>\n        <path d=\"m 78.88,300 -7,3.5 1.75,-3.5 -1.75,-3.5 z\" fill=\"#000000\" stroke=\"#000000\" stroke-miterlimit=\"10\" id=\"path28\"><\/path>\n      <\/g>\n    <\/g>\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-9\" id=\"g33\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g30\">\n        <rect x=\"0\" y=\"200\" width=\"120\" height=\"40\" fill=\"#ffffff\" stroke=\"#000000\" id=\"rect29\"><\/rect>\n      <\/g>\n      <g id=\"g32\">\n        <g id=\"g31\">\n          <switch id=\"switch30\">\n            \n            <text x=\"60\" y=\"224\" fill=\"light-dark(#000000, #ffffff)\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\" id=\"text30\">Login with Email<\/text>\n          <\/switch>\n        <\/g>\n      <\/g>\n    <\/g>\n    <g data-cell-id=\"iaSDnZpn8fB0fm1jTDTP-11\" id=\"g37\">\n      <g transform=\"translate(0.5,0.5)\" id=\"g34\">\n        <rect x=\"80\" y=\"280\" width=\"120\" height=\"40\" rx=\"20\" ry=\"20\" fill=\"#ffffff\" stroke=\"#000000\" id=\"rect33\"><\/rect>\n      <\/g>\n      <g id=\"g36\">\n        <g id=\"g35\">\n          <switch id=\"switch34\">\n            \n            <text x=\"140\" y=\"304\" fill=\"light-dark(#000000, #ffffff)\" font-family=\"Helvetica\" font-size=\"12px\" text-anchor=\"middle\" id=\"text34\">Enter Site<\/text>\n          <\/switch>\n        <\/g>\n      <\/g>\n    <\/g>\n  <\/g>\n<\/svg><\/div><\/div>\n\n\n\n<p>There are no passwords with the above approach. All the users need to remember are the email addresses that they used to register with the site access. The login and registration page looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-9.27.09-PM.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"705\" src=\"https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-9.27.09-PM-1024x705.png\" alt=\"\" class=\"wp-image-3385\" srcset=\"https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-9.27.09-PM-1024x705.png 1024w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-9.27.09-PM-300x206.png 300w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-9.27.09-PM-768x528.png 768w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-9.27.09-PM-1200x826.png 1200w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-9.27.09-PM.png 1436w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/a><figcaption class=\"wp-element-caption\">Registration and Login Page<\/figcaption><\/figure>\n\n\n\n<p>For access provisioning, I simply use a JSON file to bind the email address to the allowed web sites. Below is an example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"jdoe@gmail.com\": &#91;\n    {\n      \"user\": \"John\",\n      \"site\": \"https:\/\/site1.lufamily.ca\"\n    }\n  ],\n  \"jane.doe@icloud.com\": &#91;\n    {\n      \"user\": \"Jane\",\n      \"site\": \"https:\/\/site1.lufamily.ca\"\n    },\n    {\n      \"user\": \"Jane\",\n      \"site\": \"https:\/\/site2.lufamily.ca\"\n    }\n  ]\n}<\/code><\/pre>\n\n\n\n<p>When the user registers, they will receive an email looking like:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"915\" height=\"1024\" src=\"https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-8.25.56-PM-915x1024.png\" alt=\"\" class=\"wp-image-3391\" style=\"aspect-ratio:0.8935576092015158;width:417px;height:auto\" srcset=\"https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-8.25.56-PM-915x1024.png 915w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-8.25.56-PM-268x300.png 268w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-8.25.56-PM-768x860.png 768w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2026\/04\/Screenshot-2026-04-06-at-8.25.56-PM.png 1020w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption class=\"wp-element-caption\">Sample onboarding or registration email<\/figcaption><\/figure>\n<\/div>\n\n\n<p>In the beginning, I wrote custom code on my web site to use my identity service. However I found out I can write an Apache Lua script to check for token provisioning and invoking of the identity service. This way I can add authentication to any sites that I host with Apache2 web server with a virtual host configuration that looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;VirtualHost *:80>\n\n  &lt;Location \/ >\n    LuaCodeCache forever\n    LuaHookAccessChecker \/path\/to\/checkAuthLuFamily.lua handle\n\n    RewriteEngine On\n\n    RewriteCond %{ENV:token} ^$\n    RewriteRule ^ https:\/\/auth.lufamily.ca\/register\/%{ENV:cbsite}\" &#91;L,R=302]\n\n    Header set Set-Cookie \"token=%{token}e; Max-Age=1800; Path=\/; HttpOnly; Secure; SameSite=Strict\" env=token\n  &lt;\/Location>\n\n&lt;\/VirtualHost><\/code><\/pre>\n\n\n\n<p>The <code>checkAuthLuFamily.lua<\/code> script is used to check if a token is provided either as an <code>HTTP GET<\/code> parameter, <code>Authorization Bearer<\/code> value, or a <code>secure<\/code>, <code>http-only<\/code> cookie.  The token is actually a JWT token containing user specific attributes derived from the JSON file earlier. This token is provisioned when authentication is successful. If the token is missing, then this means the user has yet to be authenticated so we automatically redirect them to the registration page. If the token is valid, then the script will store a new refreshed token with extended expiry (another 30 minutes) into the environment variable which we use to reset the cookie. Any future requests to the same site will preserve the cookie\/token.<\/p>\n\n\n\n<p>I love this flexibility. This means I can add authentication to any site that I host with Apache2 without changing the code or modifying the site. This also means that I can develop future sites and services without having to worry about authentication.<\/p>\n\n\n\n<p>I have not provided the source code here, because I am still testing it, but I wanted to document the concept and the approach, so that I can refer to my own creation in the future.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Over the past few years I have developed several services that can be accessed using a web site. Many if not all of these sites require authentication. In the past I have typically adopted a typical user id and password technique, and more recently an email based authentication along with the user&#8217;s external IP address, &hellip; <a href=\"https:\/\/blog.lufamily.ca\/kang\/2026\/04\/06\/webauthn-with-email-implementation\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;WebAuthn with Email Implementation&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[111],"tags":[202,204,28,203],"class_list":["post-3381","post","type-post","status-publish","format-standard","hentry","category-tech","tag-apache2","tag-authentication","tag-technology","tag-webauthn"],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p7V6i8-Sx","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/posts\/3381","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/comments?post=3381"}],"version-history":[{"count":8,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/posts\/3381\/revisions"}],"predecessor-version":[{"id":3392,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/posts\/3381\/revisions\/3392"}],"wp:attachment":[{"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/media?parent=3381"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/categories?post=3381"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/tags?post=3381"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}