<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: vincenzoiozzo</title>
    <description>The latest articles on Forem by vincenzoiozzo (@vincenzoiozzo).</description>
    <link>https://forem.com/vincenzoiozzo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1001079%2F13a09d89-d2af-4203-8ed6-bd362c2c1b89.png</url>
      <title>Forem: vincenzoiozzo</title>
      <link>https://forem.com/vincenzoiozzo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vincenzoiozzo"/>
    <language>en</language>
    <item>
      <title>Passkeys Adoption Trends: Survey from Large Deployments</title>
      <dc:creator>vincenzoiozzo</dc:creator>
      <pubDate>Thu, 15 Feb 2024 11:36:32 +0000</pubDate>
      <link>https://forem.com/vincenzoiozzo/passkeys-adoption-trends-survey-from-large-deployments-4bm9</link>
      <guid>https://forem.com/vincenzoiozzo/passkeys-adoption-trends-survey-from-large-deployments-4bm9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Sign up for free on SlashID &lt;a href="https://console.slashid.dev/signup?utm_source=passkey-adoption"&gt;here&lt;/a&gt; to start implementing Passkeys in your application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In our previous &lt;a href="https://www.slashid.dev/blog/passkeys-security-implementation/"&gt;articles&lt;/a&gt;, we've extensively discussed the security benefits of passkeys—from enhancing protection against account takeovers and phishing to simplifying compliance with regulatory frameworks like PSD2. However, the user experience (UX) aspect has received less attention, raising concerns for businesses considering widespread adoption.&lt;/p&gt;

&lt;p&gt;This post synthesizes available data on passkeys deployment, focusing on conversion rates and deployment strategies, along with insights from large-scale implementations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is using passkeys?
&lt;/h2&gt;

&lt;p&gt;Global adoption data for passkeys is still emerging, but an analysis of the top 50 websites by traffic offers a glimpse into their uptake. According to Semrush's December 2023 rankings, 19 of these websites now support passkeys, either as a primary authentication method or as a secondary option for multi-factor authentication (MFA).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site&lt;/th&gt;
&lt;th&gt;Passkeys Primary&lt;/th&gt;
&lt;th&gt;Passkeys MFA&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google Search&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YouTube&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Facebook&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pornhub&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XVideos&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;X (Twitter)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wikipedia&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instagram&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reddit&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDuckGo&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yahoo&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XNXX&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TikTok&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bing&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yahoo JP&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Weather Channel&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WhatsApp&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yandex&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;xHamster&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft Online&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LinkedIn&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quora&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Twitch&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Naver&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Netflix&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Office&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VK&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Globo&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aliexpress&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CNN&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zoom&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IMDb&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;x.com (Twitter)&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New York Times&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OnlyFans&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESPN&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon.co.jp&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pinterest&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Universo Online&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eBay&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marca&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canva&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spotify&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BBC&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PayPal&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;td&gt;✕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple Inc.&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Case studies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Yahoo Japan
&lt;/h3&gt;

&lt;p&gt;Yahoo Japan, a major player with 55 million monthly active users, has seen about 11% of its user base adopt passkeys.&lt;/p&gt;

&lt;p&gt;For the cohort of users using passkeys, Yahoo Japan observed the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A statistically &lt;strong&gt;higher registration rate&lt;/strong&gt; compared to every other authentication method. In particular the increase was 2.29% on iOS, &lt;strong&gt;8.02% on Mac&lt;/strong&gt;, &lt;strong&gt;15.35% on Windows&lt;/strong&gt; and 0.66% on Android&lt;/li&gt;
&lt;li&gt;2.6x faster authentication time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;25% decrease&lt;/strong&gt; in customer support/authentication support requests&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Mercari
&lt;/h3&gt;

&lt;p&gt;Mercari, Inc. is a Japanese e-commerce company founded in 2013 with approximately 20m monthly active users. As of August 2023 approximately 900,000 users have registered passkeys &lt;a href="https://engineering.mercari.com/en/blog/entry/20230810-mercaris-passkey-adoption/"&gt;credentials&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the cohort of users using passkeys, Mercari observed the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;82.5% success rate&lt;/strong&gt; vs 67.7% for SMS OTP&lt;/li&gt;
&lt;li&gt;4.4s vs 17s average time to login with passkeys compared to SMS OTP&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Kayak
&lt;/h3&gt;

&lt;p&gt;Implementing passkeys helped KAYAK, a leading travel search engine, &lt;strong&gt;cut the average login and registration time by 50%&lt;/strong&gt;. Though specific customer support metrics are not disclosed, Kayak also saw a decrease in customer support/authentication support requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashlane
&lt;/h3&gt;

&lt;p&gt;Dashlane is a password management tool that provides a secure way to manage user credentials, access control, and authentication across multiple systems and applications. Dashlane has over 18 million users and 20,000 businesses in 180 countries.&lt;/p&gt;

&lt;p&gt;Dashlane, with its 18 million users, observed a remarkable 92% conversion rate for Passkey authentication—a 70% improvement over passwords.&lt;/p&gt;

&lt;h2&gt;
  
  
  UX considerations when implementing passkeys
&lt;/h2&gt;

&lt;p&gt;Adopting passkeys offers significant security and usability benefits but requires thoughtful implementation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Supported devices&lt;/strong&gt;: The UX of authentication and registration of a passkey is OS and browser dependent, and on some devices can be confusing, it is important to allowlist devices and browsers that have a good onboarding experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback flow when a passkey is not available&lt;/strong&gt;: Although some devices allow for syncable passkeys, this is definitely not the standard, so having a fallback authentication method for when a passkey is not available is key. However this poses both a security threat and potential friction for the user, so careful consideration needs to go into the flow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flow to add a new passkey&lt;/strong&gt;: Similar to (2), a flow to add a new passkey is key. Passkeys are still fickle today, a change in browser profile results in a lost passkey; a complete wipe of cookies on Chrome also results in a loss of the passkeys. A flow to add multiple passkeys is a must&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Passkeys represent a pivotal shift in digital authentication, offering a secure and user-friendly alternative to traditional passwords. For organizations considering passkeys, understanding both the technological landscape and user experience considerations is critical. If you're ready to explore passkeys for your business, SlashID is here to assist. Don't hesitate to &lt;a href="//mailto:contact@slashid.dev"&gt;reach out to us&lt;/a&gt; or sign up for free &lt;a href="https://console.slashid.dev/signup?utm_source=passkey-adoption"&gt;here&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>passkeys</category>
      <category>authentication</category>
      <category>webauth</category>
    </item>
    <item>
      <title>Remix authentication with SlashID</title>
      <dc:creator>vincenzoiozzo</dc:creator>
      <pubDate>Thu, 04 Jan 2024 15:40:36 +0000</pubDate>
      <link>https://forem.com/vincenzoiozzo/remix-authentication-with-slashid-2655</link>
      <guid>https://forem.com/vincenzoiozzo/remix-authentication-with-slashid-2655</guid>
      <description>&lt;h2&gt;
  
  
  Meet &lt;code&gt;@slashid/remix&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@slashid/remix&lt;/code&gt; is our Remix-first identity SDK. We've borrowed the power of our React SDK and aligned it with Remix's unique design patterns.&lt;/p&gt;

&lt;p&gt;We've built the &lt;a href="https://github.com/slashid/javascript/tree/main/packages/remix#readme"&gt;SlashID Remix SDK&lt;/a&gt; with ease-of-use and developer experience as our core design pillar.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@slashid/remix&lt;/code&gt; is our easiest to use SDK - ever! Forget about navigating through complex API documentation. &lt;code&gt;@slashid/remix&lt;/code&gt; provides the smallest API surface possible so that it stays out of your way, enabling you to get productive fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Server side rendering (SSR)
&lt;/h3&gt;

&lt;p&gt;Wave goodbye to loading spinners. Remix is all about parallelizing data fetching for optimized rendering. Our components and authentication logic run server-side so you can optimise your first render based on the users authentication state. Render once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Protected Pages
&lt;/h3&gt;

&lt;p&gt;Gate pages behind login, or protect pages from users without sufficient privilege to view them using &lt;a href="https://developer.slashid.dev/docs/access/guides/suborgs/multitenancy-example#groups"&gt;Groups&lt;/a&gt; - on the server and/or in the client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-built log-in &amp;amp; sign-up form
&lt;/h3&gt;

&lt;p&gt;Render your login form with just three lines of code. Magic links, passkeys, OTP, SAML, OIDC &amp;amp; passwords - we've got you covered.&lt;/p&gt;

&lt;p&gt;Not to your taste? Our form appearance can be completely customised - and we mean *completely*. Check out &lt;a href="https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-form#ui-customization"&gt;Form: CSS custom properties&lt;/a&gt;, and &lt;a href="https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-form#layout-slots--primitives"&gt;Form: Layout Slots &amp;amp; Primitives&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auth-aware conditional rendering
&lt;/h3&gt;

&lt;p&gt;Use the &lt;code&gt;&amp;lt;LoggedIn&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;LoggedOut&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;Groups&amp;gt;&lt;/code&gt; control components to conditionally render parts of a page depending on a users authentication state or group membership.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SDK in Action
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;You will need to &lt;a href="https://console.slashid.dev/"&gt;sign up to SlashID&lt;/a&gt; and create an organization, once you've complete this you'll find your organization ID in &lt;a href="https://console.slashid.dev/settings/general"&gt;Settings -&amp;gt; General&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Your environment should have the following dependencies installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;node.js&lt;/code&gt; =&amp;gt; &lt;code&gt;&amp;gt;=20.x.x&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react&lt;/code&gt; =&amp;gt; &lt;code&gt;&amp;gt;=18.x.x&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Install &lt;code&gt;@slashid/remix&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Once you have a Remix application ready to go, you need to install the SlashID Remix SDK. This SDK provides a prebuilt log-in &amp;amp; sign-up form, control components and hooks - tailor made for Remix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @slashid/remix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create SlashID app primitives
&lt;/h3&gt;

&lt;p&gt;In your Remix application create a file &lt;code&gt;app/slashid.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this file create the SlashID application primitives and re-export them, you'll use them later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/slashid.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createSlashIDApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@slashid/remix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;SlashIDApp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;slashIDRootLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;slashIDLoader&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSlashIDApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_ORGANIZATION_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;oid&lt;/code&gt; is your organization ID, you can find it in the &lt;a href="https://console.slashid.dev/"&gt;SlashID console&lt;/a&gt; under &lt;a href="https://console.slashid.dev/settings/general"&gt;Settings -&amp;gt; General&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Configure &lt;code&gt;slashIDRootLoader&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;To configure SlashID in your Remix application you'll need to update your root loader. With a small change you'll have easy access to authentication in all of your Remix routes.&lt;/p&gt;

&lt;p&gt;Import the &lt;code&gt;slashIDRootLoader&lt;/code&gt; you created in the previous step, invoke it and export it as &lt;code&gt;loader&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// root.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LoaderFunction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Links&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;LiveReload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Outlet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Scripts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ScrollRestoration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useLoaderData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slashIDRootLoader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/slashid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoaderFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;slashIDRootLoader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;charSet&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Meta&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Links&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Outlet&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollRestoration&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Scripts&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LiveReload&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to load additional data via the root loader, you can simply pass a loader function to &lt;code&gt;slashIDRootLoader&lt;/code&gt;. You can even check for authentication right here in the root loader.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// root.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slashIDRootLoader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/slashid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@slashid/remix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoaderFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;slashIDRootLoader&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// the user is logged in&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// fetch data&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// your data&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Wrap your application body with &lt;code&gt;&amp;lt;SlashIDApp&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;In step 2 you created &lt;code&gt;SlashIDApp&lt;/code&gt;, it provides authentication state to your React component tree. There is no configuration, just add it to your Remix application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// root.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SlashIDApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~/slashid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;charSet&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Meta&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Links&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Wrap the contents of your &amp;lt;body&amp;gt; with SlashIDApp */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SlashIDApp&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Outlet&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollRestoration&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Scripts&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LiveReload&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SlashIDApp&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Create your log-in/sign-up page
&lt;/h3&gt;

&lt;p&gt;SlashID offers a prebuilt form that works for both log-in and sign-up. Users with an account can log-in here and users without one need to simply complete the form to create their account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/login.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ConfigurationProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Factor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@slashid/remix&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;factors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Factor&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email_link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LogIn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;500px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ConfigurationProvider&lt;/span&gt; &lt;span class="na"&gt;factors&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;factors&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ConfigurationProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Protecting your pages
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Server side
&lt;/h4&gt;

&lt;p&gt;To protect your routes you can use the &lt;code&gt;slashIDLoader&lt;/code&gt; you created in step 2. This utility is a wrapper for your loaders that provides authentication state to your loader code.&lt;/p&gt;

&lt;p&gt;You'll check if the user exists, and if not redirect them to the login page.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;useSlashID()&lt;/code&gt; hook provides authentication state &amp;amp; helper functions to your React code, here you'll implement &lt;code&gt;logOut&lt;/code&gt; too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/_index.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slashIDLoader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~/slashid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useSlashID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@slashid/remix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;slashIDLoader&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;logOut&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSlashID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Index&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;You are logged in!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;logOut&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Log out
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Client side
&lt;/h4&gt;

&lt;p&gt;SlashID has several &lt;a href="https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-loggedin"&gt;Control Components&lt;/a&gt; that allow you to conditionally show or hide content based on the users authentication state.&lt;/p&gt;

&lt;p&gt;You'll implement &lt;code&gt;&amp;lt;LoggedIn&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;LoggedOut&amp;gt;&lt;/code&gt;, and provide the option to log-out by implementing the &lt;code&gt;logOut&lt;/code&gt; helper function from the &lt;code&gt;useSlashID()&lt;/code&gt; hook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/_index.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LoggedIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoggedOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useSlashID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@slashid/remix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useNavigate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;logOut&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSlashID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;navigate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useNavigate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LoggedIn&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        You are logged in!
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;logOut&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Log out
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;LoggedIn&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LoggedOut&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;LoggedOut&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Learn more &lt;a href="https://www.slashid.dev/blog/remix-sdk-v1/"&gt;here&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>remix</category>
      <category>authentication</category>
      <category>react</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>GDPR Compliance: Consent Management with SlashID</title>
      <dc:creator>vincenzoiozzo</dc:creator>
      <pubDate>Fri, 03 Nov 2023 18:30:11 +0000</pubDate>
      <link>https://forem.com/vincenzoiozzo/gdpr-compliance-consent-management-with-slashid-51io</link>
      <guid>https://forem.com/vincenzoiozzo/gdpr-compliance-consent-management-with-slashid-51io</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GWrBMala--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/gdpr-consent-management-react/placeholder.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GWrBMala--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/gdpr-consent-management-react/placeholder.png" alt="React form" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;At SlashID we make it easy for you to collect, store and process user data in a way that complies with the European Unions' &lt;a href="https://developer.slashid.dev/docs/access/concepts/data-protection-compliance/gdpr"&gt;General Data Protection Regulation (GDPR) and ePrivacy Directive&lt;/a&gt; legislations. We deeply value your users' privacy.&lt;/p&gt;

&lt;p&gt;In this blog post, we're excited to introduce the &lt;code&gt;&amp;lt;GDPRConsentDialog&amp;gt;&lt;/code&gt; component from the SlashID React SDK. As a companion for &lt;a href="https://developer.slashid.dev/docs/access/concepts/replication#:~:text=At%20SlashID%2C%20we%20solve%20these%20problems%20transparently%20for%20you.%20All%20user%20data%20is%20by%20default%20stored%20in%20the%20region%20geographically%20closest%20to%20the%20user.%20Additionally%2C%20we%20globally%20replicate%20some%20user%20data%20in%20hashed%20form%20to%20ensure%20fast%20reads%2C%20while%20remaining%20compliant%20and%20secure."&gt;our data residency features&lt;/a&gt;, the &lt;code&gt;&amp;lt;GDPRConsentDialog&amp;gt;&lt;/code&gt; component makes GDPR compliance effortless and worry-free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Collecting user consents
&lt;/h2&gt;

&lt;p&gt;We store consents in our &lt;a href="https://www.slashid.dev/products/vault/"&gt;Data Vault&lt;/a&gt;, making them readily available anywhere you implement SlashID.&lt;/p&gt;

&lt;p&gt;Consent is broken down into five high-level categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Analytics&lt;/li&gt;
&lt;li&gt;Marketing&lt;/li&gt;
&lt;li&gt;Retargeting&lt;/li&gt;
&lt;li&gt;Tracking&lt;/li&gt;
&lt;li&gt;Necessary cookies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By default the &lt;code&gt;&amp;lt;GDPRConsentDialog&amp;gt;&lt;/code&gt; prompts the user to &lt;code&gt;Accept all&lt;/code&gt; or &lt;code&gt;Reject all&lt;/code&gt;; where reject all also includes rejecting "necessary cookies".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;GDPRConsentDialog&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UPMeTJKt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/gdpr-consent-management-react/default.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UPMeTJKt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/gdpr-consent-management-react/default.png" alt="The default GDPRConsentDialog state" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Customize&lt;/code&gt; button reveals a set of controls where a user can choose by category what consent they are comfortable to give.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EkkbFYkx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/gdpr-consent-management-react/customize.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EkkbFYkx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/gdpr-consent-management-react/customize.png" alt="Consent can be customized" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes a subset of cookies are necessary to provide a service or comply with legal requirements, like data protection laws.&lt;/p&gt;

&lt;p&gt;Enabling this is as simple as setting the &lt;code&gt;necessaryCookiesRequired&lt;/code&gt; prop when implementing the &lt;code&gt;&amp;lt;GDPRConsentDialog&amp;gt;&lt;/code&gt; component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;GDPRConsentDialog&lt;/span&gt; &lt;span class="na"&gt;necessaryCookiesRequired&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kYH4YzpQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/gdpr-consent-management-react/necessary.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kYH4YzpQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/gdpr-consent-management-react/necessary.png" alt="Necessary cookies can be required" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've built the &lt;code&gt;&amp;lt;GDPRConsentDialog&amp;gt;&lt;/code&gt; with maximum flexibility in mind.&lt;/p&gt;

&lt;p&gt;If you have a more complex use case like custom consent levels when choosing &lt;code&gt;Accept all&lt;/code&gt; and &lt;code&gt;Reject all&lt;/code&gt;, or disabling interaction with your product until consent has been given - you're in luck! We have support for these edge-cases and more: &lt;a href="https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-gdprconsentdialog"&gt;check out the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading and writing consents programatically
&lt;/h2&gt;

&lt;p&gt;Sometimes there are actions in your product which are gated behind a users consent, for example publishing events to your analytics platform.&lt;/p&gt;

&lt;p&gt;With SlashID you can access a users consents with the &lt;code&gt;useGDPRConsent()&lt;/code&gt; hook, and use it to decide whether or not the action can be performed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useGDPRConsent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GDPRConsentLevel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@slashid/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAnalytics&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;consents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useGDPRConsent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;publish&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useAnalytics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading consent...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analyticsAllowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;consents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;consent_level&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;consent_level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;GDPRConsentLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Analytics&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addToCart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analyticsAllowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add_to_cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;addToCart&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Add to cart&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might find yourself in a position where you would like to offer your users the option to "upgrade" their consent and opt-in to some functionality.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;useGDPRConsentHook()&lt;/code&gt; hook provides you a mechanism to do just that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useGDPRConsent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GDPRConsentLevel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@slashid/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAnalytics&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;consents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateGdprConsent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useGDPRConsent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading consent...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analyticsAllowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;consents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;consent_level&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;consent_level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;GDPRConsentLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Analytics&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enableAnalytics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;updateGdprConsent&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;consents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;GDPRConsentLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Analytics&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;analyticsAllowed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          You have analytics disabled, your user dashboard has insufficient data to operate.
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;enableAnalytics&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Enable analytics
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this blogpost we explained how to collect user consent with the &lt;code&gt;&amp;lt;GDPRConsentDialog&amp;gt;&lt;/code&gt; and use the data from &lt;code&gt;useGDPRConsent()&lt;/code&gt; to make informed decisions within your product, GDPR compliance has never been easier.&lt;/p&gt;

&lt;p&gt;Ready to try SlashID? Sign up &lt;a href="https://console.slashid.dev/signup"&gt;here&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Is there a feature you’d like to see, or have you tried out SlashID and have some feedback? &lt;a href="//mailto:contact@slashid.dev"&gt;Let us know&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>gdpr</category>
      <category>compliance</category>
      <category>react</category>
    </item>
    <item>
      <title>JWT Implementation Pitfalls, Security Threats, and Our Approach to Mitigate Them</title>
      <dc:creator>vincenzoiozzo</dc:creator>
      <pubDate>Sat, 23 Sep 2023 14:47:41 +0000</pubDate>
      <link>https://forem.com/vincenzoiozzo/jwt-implementation-pitfalls-security-threats-and-our-approach-to-mitigate-them-11cj</link>
      <guid>https://forem.com/vincenzoiozzo/jwt-implementation-pitfalls-security-threats-and-our-approach-to-mitigate-them-11cj</guid>
      <description>&lt;h2&gt;
  
  
  JSON Web Tokens
&lt;/h2&gt;

&lt;p&gt;There are many excellent introductions to JWTs, so for the purposes of this discussion we will focus on the structure.&lt;/p&gt;

&lt;p&gt;JWTs are typically transmitted as base-64 encoded strings, and are composed of three parts separated by periods:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A header containing metadata about the token itself&lt;/li&gt;
&lt;li&gt;The payload, a JSON-formatted set of claims&lt;/li&gt;
&lt;li&gt;A signature that can be used to verify the contents of the payload&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, this JWT&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;eyJhbGciOiJIUzI&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;NiIsInR&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;cCI&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;IkpXVCJ&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="err"&gt;.eyJzdWIiOiIxMjM&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;NTY&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;ODkwIiwibmFtZSI&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;IkpvZSBTbGFzaElEIiwiaWF&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;IjoxNTE&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;MjM&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;MDIyfQ.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&gt;cL&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="err"&gt;NsNCXLPEvmvNGxHN&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;wLuarpp&lt;/span&gt;&lt;span class="mi"&gt;98&lt;/span&gt;&lt;span class="err"&gt;wwezHnSt&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;fqg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;comprises the following parts&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Encoded value&lt;/th&gt;
&lt;th&gt;Decoded value&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Header&lt;/td&gt;
&lt;td&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9&lt;/td&gt;
&lt;td&gt;{ "alg": "HS256", "typ": "JWT"}&lt;/td&gt;
&lt;td&gt;Indicates that this is a JWT and that it was hashed with the HS256 algorithm (HMAC using SHA-256)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payload&lt;/td&gt;
&lt;td&gt;eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvZSBTbGFzaElEIiwiaWF0IjoxNTE2MjM5MDIyfQ&lt;/td&gt;
&lt;td&gt;{"sub": "1234567890", "name": "SlashID User", "iat": 1516239022}&lt;/td&gt;
&lt;td&gt;Payload with claims about a user and the token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signature&lt;/td&gt;
&lt;td&gt;4cL42NsNCXLPEvmvNGxHN3wLuarpp98wwezHnSt2fqg&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;The signature generated using the HS256 algorithm that verifies the payload&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important aspect of this is that the JWT is &lt;strong&gt;signed, meaning that the claims in the payload can be verified&lt;/strong&gt;, if one has access to the appropriate cryptographic key.&lt;/p&gt;

&lt;p&gt;The algorithm used for the signature is stored in the Header (&lt;code&gt;alg&lt;/code&gt;) and as we'll see later in the article, this becomes the source of a lot of issues with JWTs.&lt;/p&gt;

&lt;p&gt;The signature of a JWT token is calculated as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;signAndhash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64UrlEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;base64UrlEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;signAndhash&lt;/code&gt; is the signing and hashing algorithms specified in the &lt;code&gt;alg&lt;/code&gt; header field. The &lt;a href="https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms"&gt;JOSE IANA&lt;/a&gt; page contains the list of supported algorithms.&lt;/p&gt;

&lt;p&gt;In the example above HS256 stands for HMAC using SHA-256 and the &lt;code&gt;secret&lt;/code&gt; is a 256-bit key.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Signing is &lt;em&gt;not&lt;/em&gt; the same as encryption - even without the cryptographic key for verifying, anybody can decode the token payload and inspect the contents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Naming convention
&lt;/h2&gt;

&lt;p&gt;There are many standards associated with JWTs, it is useful to clarify a few different formats as we'll use them throughout the article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JWT (JSON Web Token): JSON-based claims format using JOSE for protection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JOSE (Javascript Object Signing and Encryption): set of open standards, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JWS (JSON Web Signature)&lt;/strong&gt;: JOSE standard for cryptographic authentication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWE (JSON Web Encryption)&lt;/strong&gt;: JOSE standard for encryption&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWA (JSON Web Algorithms)&lt;/strong&gt;: cryptographic algorithms for use in JWS/JWE&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWK (JSON Web Keys)&lt;/strong&gt;: JSON-based format to represent JOSE keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The JWT specification is relatively limited as it only defines the format for representing information ("claims") as a JSON object that can be transferred between two parties.&lt;br&gt;
In practice, the JWT spec is extended by both the JSON Web Signature (JWS) and JSON Web Encryption (JWE) specifications, which define concrete ways of actually implementing JWTs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of a JWT-related bug
&lt;/h2&gt;

&lt;p&gt;A key aspect of JWTs, and one of the reasons why they have become so popular, is that they can be used in a stateless manner. In other words, the server doesn't store a copy of the JWTs it mints. As a result, to check the validity of the token both the client and the server need to verify their signature. The validity of the signature is how we prove the integrity of the token.&lt;/p&gt;

&lt;p&gt;Generally vulnerabilities in JWT implementations rely on either a failure to validate a token signature, a signature bypass, or a weak/insecure secret used to encrypt or sign a token.&lt;/p&gt;

&lt;p&gt;Fundamentally three design choices make JWT implementations prone to issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deciding the decryption/validation algorithm based on untrusted ciphertext&lt;/li&gt;
&lt;li&gt;Allowing broken algorithms (RSA PKCS#1 v1.5 encryption) and "none"&lt;/li&gt;
&lt;li&gt;Allowing for very complex signing options. For instance, supporting X.509 Certificate Chain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's see some of the most common issues with JWTs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "none" Algorithm
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;none&lt;/code&gt; algorithm is intended to be used for situations where the integrity of the token has already been verified. Unfortunately, some libraries treat tokens signed with the &lt;code&gt;none&lt;/code&gt; algorithm as a valid token with a verified signature. This would allow an attacker to bypass signature checks and mint valid JWT tokens.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Solution: Always sanitize the &lt;code&gt;alg&lt;/code&gt; field and reject tokens signed with the &lt;code&gt;none&lt;/code&gt; algorithm.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  "Billion hashes attack"
&lt;/h3&gt;

&lt;p&gt;Tervoort recently disclosed at Black Hat a new attack &lt;a href="https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf"&gt;pattern&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;JWT tokens support various families of &lt;code&gt;PBES2&lt;/code&gt; as signing/encryption algorithms. The &lt;code&gt;p2c&lt;/code&gt; header parameter is required when using &lt;code&gt;PBES2&lt;/code&gt; and it is used to specify the PBKDF2 iteration &lt;a href="https://en.wikipedia.org/wiki/PBKDF2"&gt;count&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An unauthenticated attacker could use the parameter to DoS a server by specifing a very large &lt;code&gt;p2c&lt;/code&gt; value resulting in billions of hashing function iterations per verification attempt.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Solution: Always sanitize the &lt;code&gt;p2c&lt;/code&gt; parameter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Brute-forcing or stealing secret keys
&lt;/h3&gt;

&lt;p&gt;Some signing algorithms, such as HS256 (HMAC + SHA-256), use an arbitrary string as the secret key. It's crucial that this secret can't be easily guessed, brute-forced by an attacker or stolen.&lt;/p&gt;

&lt;p&gt;An attacker with the secret key would be able to create JWTs with any header and payload values they like, then use the key to re-sign the token with a valid signature.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Solution: Avoid weak secret keys, implement frequent key rotation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Algorithm confusion
&lt;/h3&gt;

&lt;p&gt;As discussed, JWTs support a variety of different algorithms (including some broken ones) with significantly different verification processes. Many libraries provide a single, algorithm-agnostic method for verifying signatures. These methods rely on the &lt;code&gt;alg&lt;/code&gt; parameter in the token's header to determine the type of verification they should perform.&lt;/p&gt;

&lt;p&gt;Problems arise when developers use a generic signature method and assume that it will exclusively handle JWTs signed using an asymmetric algorithm like &lt;code&gt;RS256&lt;/code&gt;. Due to this flawed assumption, they may end up in a "type confusion" type of scenario. Specifically, a scenario where the public key of a keypair is used as an HMAC secret for a symmetric cypher instead.&lt;/p&gt;

&lt;p&gt;An attacker in this case can send a token signed using a symmetric algorithm like &lt;code&gt;HS256&lt;/code&gt; instead of an asymmetric one. This means that an attacker could sign the token using HS256 and the static public key used by the server to verify signatures, and the server will use the same public key to verify the &lt;strong&gt;symmetric&lt;/strong&gt; signature thus completely bypassing the signature verification process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Solution: Always verify the alg parameter and ensure that the key passed to the verification function matches the type of algorithm used for the signature.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Key injection/self-signed JWT
&lt;/h3&gt;

&lt;p&gt;Although only the &lt;code&gt;alg&lt;/code&gt; parameter is mandatory for a token, JWT headers often contain several other parameters. Some of the more common ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;jwk&lt;/strong&gt; (JSON Web Key) - Provides an embedded JSON object representing the key.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;jku&lt;/strong&gt; (JSON Web Key Set URL) - Provides a URL from which servers can fetch a set of keys to verify signatures.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;kid&lt;/strong&gt; (Key ID) - Provides an ID that servers can use to identify the correct key in cases where there are multiple keys to choose from.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These user-controllable parameters each tell the recipient server which key to use when verifying the signature.&lt;/p&gt;

&lt;h4&gt;
  
  
  Injecting self-signed JWTs via the jwk parameter
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;jwk&lt;/code&gt; header parameter allows an attacker to specify an arbitrary key to verify the signature of a token. Servers should only use a limited allow-list of public keys to verify JWT signatures. However, default implementations of JWT verification libraries allow for arbitrary signatures to be used hence the developer has to allow-list specific keys or otherwise an attacker could bypass the signature verification process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Solution: Disallow the usage of &lt;code&gt;jwk&lt;/code&gt; or have an allow-list of valid keys.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Injecting self-signed JWTs via the jku parameter
&lt;/h4&gt;

&lt;p&gt;Similar to the example below it is crucial that the keys passed to the verification function via the &lt;code&gt;jku&lt;/code&gt; parameters are part of an allow-list. Further the implementer should also&lt;br&gt;
have an allow-list of domains and valid TLS certificates for those domains.&lt;/p&gt;

&lt;p&gt;In fact, JWK Sets like this are often exposed publicly via a standard endpoint, such as &lt;code&gt;/.well-known/jwks.json&lt;/code&gt; - if a domain is subject to a watering-hole attack or the verification function doesn't verify the domain an attacker is able to bypass signature verification.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Solution: Disallow the usage of &lt;code&gt;jku&lt;/code&gt;` or have an allow-list of valid keys, trusted domains, and valid TLS certificates for those domains.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Injecting self-signed JWTs via the kid parameter
&lt;/h4&gt;

&lt;p&gt;Verification keys are often stored as a JWK Set. In this case, the server may simply look for the JWK with the same &lt;code&gt;kid&lt;/code&gt; as the token. However, the &lt;code&gt;kid&lt;/code&gt; is an arbitrary string and it's up to the developer to decide how to use it to find the correct key in the JWK Set.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;kid&lt;/code&gt; parameter could be used for a command injection attack without proper sanitization. For example, an attacker might be able to use it to force a directory traversal attack pointing the verification function to a static, well-known file like &lt;code&gt;/dev/null&lt;/code&gt; which would result in an empty string used for verification and often result in a signature bypass.&lt;/p&gt;

&lt;p&gt;Another example is if the server stores its verification keys in a database, the &lt;code&gt;kid&lt;/code&gt; parameter is also a potential vector for SQL injection attacks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Solution: Always sanitize the &lt;code&gt;kid&lt;/code&gt; parameter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The SlashID approach
&lt;/h2&gt;

&lt;p&gt;The issues above are just some of the common ones found in libraries, but many others keep coming up due to the design flaws described above. At SlashID, we strive to help customers secure their identities so we took a principled approach to the problem by abstracting it away for developers.&lt;/p&gt;

&lt;p&gt;In a previous blogpost we've discussed some of the implementation details of our we mint and verify JWT &lt;a href="https://www.slashid.dev/blog/tink-jwt-ecdsa/"&gt;tokens&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But we've gone further and added a JWT verification plugin to &lt;a href="https://www.gateproject.io/"&gt;Gate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The verification plugin works both with tokens issued by SlashID as well as tokens issued by a &lt;a href="https://developer.slashid.dev/docs/gate/plugins/validate-jwt"&gt;third-party&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To address the issues described above, we employ several countermeasures:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Only support pre-defined signing algorithms&lt;/li&gt;
&lt;li&gt;Rotate signing keys frequently and adopt a vaulting solution to store the private key&lt;/li&gt;
&lt;li&gt;Verify and pin TLS certificates for JWKS&lt;/li&gt;
&lt;li&gt;Maintain an allow-list of valid domains&lt;/li&gt;
&lt;li&gt;Disallow unsafe header parameters&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By deploying Gate with the JWT verification plugin, developers can offload the complexity and risk of verifying JWT tokens to SlashID.&lt;/p&gt;

&lt;p&gt;Let's see an example in &lt;a href="https://www.slashid.dev/blog/jwt-risks/#gate-configuration"&gt;action&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this brief post we've shown how JWT tokens while ubiquitous and simple-looking at first, are fraught with risk. Gate is an effective and easy way to offload that&lt;br&gt;
effort from application developers.&lt;/p&gt;

&lt;p&gt;We’d love to hear any feedback you may have! Try out Gate with a &lt;a href="https://console.slashid.dev/signup/gate"&gt;free account&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Protecting Exposed APIs: Avoid Data Leaks with SlashID Gate and OPA</title>
      <dc:creator>vincenzoiozzo</dc:creator>
      <pubDate>Wed, 13 Sep 2023 15:41:25 +0000</pubDate>
      <link>https://forem.com/vincenzoiozzo/protecting-exposed-apis-avoid-data-leaks-with-slashid-gate-and-opa-ie6</link>
      <guid>https://forem.com/vincenzoiozzo/protecting-exposed-apis-avoid-data-leaks-with-slashid-gate-and-opa-ie6</guid>
      <description>&lt;h2&gt;
  
  
  How did the Duolingo leak happen?
&lt;/h2&gt;

&lt;p&gt;In March, Ivano Somaini wrote a tweet disclosing an unauthenticated Duolingo &lt;a href="https://twitter.com/IvanoSomaini/status/1631168225399525376?t=4xIAYDiwSenj1WUb5NjFcg&amp;amp;s=03"&gt;API&lt;/a&gt; as part of his Open Source Intelligence (OSINT) work.&lt;/p&gt;

&lt;p&gt;The issue is pretty straightforward. A simple API call to the &lt;a href="https://www.duolingo.com/2017-06-30/users?email"&gt;https://www.duolingo.com/2017-06-30/users?email&lt;/a&gt; endpoint reveals several private details about users and allows attackers to enumerate registered emails. Below an example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"joinedClassroomIds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"streak"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"motivation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"acquisitionSurveyReason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"shouldForceConnectPhoneNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"picture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"//simg-ssl.duolingo.com/avatar/default_2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"learningLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ru"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hasFacebookId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"shakeToReportEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"liveOpsFeatures"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"startTimestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1693007940&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TIMED_PRACTICE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"endTimestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1693180740&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"canUseModerationTools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;184078602543312&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"betaStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INELIGIBLE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hasGoogleId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"privacySettings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fromLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hasRecentActivity15"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"_achievements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"observedClassroomIds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"profileCountry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"chinaUserModerationRecords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"globalAmbassadorStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"currentCourseId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DUOLINGO_RU_EN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hasPhoneNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"creationDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;146229322008&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"achievements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hasPlus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"roles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"classroomLeaderboardsEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"emailVerified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"courses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"preload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"placementTestAvailable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"authorId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"duolingo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Russian"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"learningLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ru"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"xp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;370&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"healthEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"fromLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"crowns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DUOLINGO_RU_EN"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"totalXp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;370&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"streakData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"currentStreak"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Armed with this API, an attacker published a dump of 2.6 million user records on VX-Underground.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This kind of incident is far from isolated, and Duolingo is just one of the many examples. In a similar incident in 2021, the "Add Friend" API allowed linking phone numbers to user accounts, costing Facebook over $275 million in fines from the Irish Data Protection Commission.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introducing Gate
&lt;/h2&gt;

&lt;p&gt;At SlashID, we believe that security begins with Identity. Gate is our edge access control service to protect APIs and workloads.&lt;/p&gt;

&lt;p&gt;Gate can be used to monitor or enforce authentication, authorization and identity-based rate limiting on APIs and workloads, as well as to detect, anonymize, or block personally identifiable information (PII) exposed through your APIs or workloads.&lt;/p&gt;

&lt;p&gt;Read on to learn how to deploy Gate to prevent data breaches like the ones mentioned above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying Gate
&lt;/h2&gt;

&lt;p&gt;Gate can be deployed in multiple ways: as a sidecar for your service, as an external authorizer for Envoy, an ingress proxy or a plugin for your favorite API Gateway. See more in the Gate &lt;a href="https://docs.gateproject.io/docs/gate/configuration"&gt;configuration&lt;/a&gt; docs.&lt;/p&gt;

&lt;p&gt;For this toy example we chose a simple Docker Compose deployment, which looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8000:8000&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PORT=8000&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;envs/env.env&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on-failure&lt;/span&gt;

  &lt;span class="na"&gt;gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;slashid/gate:latest&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./gate.yaml:/gate/gate.yaml&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:8080&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;envs/env.env&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--yaml /gate/gate.yaml&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on-failure&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Docker Compose spawns two services: Gate and a toy backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simulating the leaky API
&lt;/h2&gt;

&lt;p&gt;Our toy backend contains a REST API that behaves similarly to the Duolingo one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'test@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Test User'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'John Doe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;# ... add more users if needed
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user_by_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/get_user/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"business"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(...,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"The email of the user to search for"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_user_by_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"User not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'http://gate:8080/get_user/?email=test@example.com'&lt;/span&gt; | jq
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"email"&lt;/span&gt;: &lt;span class="s2"&gt;"test@example.com"&lt;/span&gt;,
  &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"Test User"&lt;/span&gt;,
  &lt;span class="s2"&gt;"id"&lt;/span&gt;: 1
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Detecting PII data through Gate
&lt;/h2&gt;

&lt;p&gt;Gate has a plugin-based architecture and we expose several built-in plugins. In particular, the PII Anonymizer plugin allows the detection and anonymization of PII or other sensitive data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The PII Anonymizer plugin can be configured to exclusively monitor PII (as opposed to editing the traffic) by setting the &lt;code&gt;anonymizers&lt;/code&gt; rule to &lt;code&gt;keep&lt;/code&gt;. We'll show an example in the next section.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's see a simple Gate configuration that detects email addresses and rewrites the HTTP response to anonymize the field with a hash of the email address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
    &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;

  &lt;span class="na"&gt;plugins_http_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
      &lt;span class="na"&gt;cache_control_override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;private, max-age=600, stale-while-revalidate=300&lt;/span&gt;

  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pii_anonymizer&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anonymizer&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;intercept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;request_response&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;anonymizers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;EMAIL_ADDRESS:&lt;/span&gt;
            &lt;span class="s"&gt;type: hash&lt;/span&gt;

  &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*/get_user'&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://backend:8000&lt;/span&gt;
      &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;pii_anonymizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'http://gate:8080/api/get_user/?email=test@example.com'&lt;/span&gt; | jq
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"email"&lt;/span&gt;: &lt;span class="s2"&gt;"973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b"&lt;/span&gt;,
  &lt;span class="s2"&gt;"id"&lt;/span&gt;: 1,
  &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"Test User"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Detecting PII and blocking the request with OPA
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: similarly to the PII detection plugin, the OPA plugin can also be run in monitoring mode. See the end of the blogpost to find out more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes hashing the request is not enough and you want to block it entirely, let's see how to combine the PII detection plugin with the OPA plugin to detect and block requests containing PII data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: In the examples below we embed the OPA policies directly in the Gate config but they can also be served through a bundle, please check out our documentation to learn more about the plugin.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
    &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;

  &lt;span class="na"&gt;plugins_http_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
      &lt;span class="na"&gt;cache_control_override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;private, max-age=600, stale-while-revalidate=300&lt;/span&gt;

  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authz_deny_pii&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;opa&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;intercept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;response&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*slashid_config&lt;/span&gt;
        &lt;span class="na"&gt;policy_decision_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/authz/allow&lt;/span&gt;
        &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;package authz&lt;/span&gt;

          &lt;span class="s"&gt;import future.keywords.if&lt;/span&gt;

          &lt;span class="s"&gt;default allow := false&lt;/span&gt;

          &lt;span class="s"&gt;no_key_found(obj, key) {&lt;/span&gt;
            &lt;span class="s"&gt;not obj[key]&lt;/span&gt;
          &lt;span class="s"&gt;}&lt;/span&gt;

          &lt;span class="s"&gt;allow if no_key_found(input.response.http.headers,  "X-Gate-Anonymize-1")&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pii_anonymizer&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anonymizer&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;intercept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;request_response&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;anonymizers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;DEFAULT:&lt;/span&gt;
            &lt;span class="s"&gt;type: keep&lt;/span&gt;
  &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*/get_user'&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://backend:8000&lt;/span&gt;
      &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;pii_anonymizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;authz_deny_pii&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;authz_deny_pii&lt;/code&gt; instance of the OPA plugin enforces an OPA policy that blocks a request if the response contains a &lt;code&gt;X-Gate-Anonymize-1&lt;/code&gt;. This is a header added by the PII detection plugin to notify of the presence of PII.&lt;/p&gt;

&lt;p&gt;Let's see it in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/server/app &lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="s1"&gt;'http://gate:8080/api/get_user/?email=test@example.com'&lt;/span&gt; | jq
&lt;span class="k"&gt;*&lt;/span&gt; processing: http://gate:8080/api/get_user/?email&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;@example.com
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:--     0&lt;span class="k"&gt;*&lt;/span&gt;   Trying 172.27.0.5:8080...
&lt;span class="k"&gt;*&lt;/span&gt; Connected to gate &lt;span class="o"&gt;(&lt;/span&gt;172.27.0.5&lt;span class="o"&gt;)&lt;/span&gt; port 8080
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; GET /api/get_user/?email&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;@example.com HTTP/1.1
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Host: gate:8080
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; User-Agent: curl/8.2.1
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Accept: &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&amp;lt; HTTP/1.1 403 Forbidden
&amp;lt; Cache-Control: private, max-age&lt;span class="o"&gt;=&lt;/span&gt;600, stale-while-revalidate&lt;span class="o"&gt;=&lt;/span&gt;300
&amp;lt; Content-Length: 0
&amp;lt; Content-Type: application/json
&amp;lt; Date: Sat, 02 Sep 2023 13:58:00 GMT
&amp;lt; Server: uvicorn
&amp;lt; Via: 1.0 gate
&amp;lt; X-Gate-Anonymize-1: &lt;span class="nv"&gt;$.&lt;/span&gt;body.email 0 64 EMAIL_ADDRESS
&amp;lt;
  0     0    0     0    0     0      0      0 &lt;span class="nt"&gt;--&lt;/span&gt;:--:--  0:00:01 &lt;span class="nt"&gt;--&lt;/span&gt;:--:--     0
&lt;span class="k"&gt;*&lt;/span&gt; Connection &lt;span class="c"&gt;#0 to host gate left intact&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that in this example &lt;code&gt;pii_anonymizer&lt;/code&gt; is set to monitoring mode: &lt;code&gt;type: keep&lt;/code&gt; for all PII types (&lt;code&gt;DEFAULT&lt;/code&gt;). The plugin allows PII to pass through unchanged, without replacing it with an anonymized version of the data or changing the traffic in any way.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pii_anonymizer&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anonymizer&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;intercept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;request_response&lt;/span&gt;
  &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;anonymizers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;DEFAULT:&lt;/span&gt;
        &lt;span class="s"&gt;type: keep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Differential policy enforcement for authenticated users
&lt;/h2&gt;

&lt;p&gt;Let's now enforce a new OPA policy that blocks requests containing PII only if the user is not authenticated, while allowing PII in requests of authenticated users.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For simplicity, in this example we'll use SlashID Access to handle authentication, but any Identity Provider (IdP) would be suitable.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
    &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;

  &lt;span class="na"&gt;plugins_http_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
      &lt;span class="na"&gt;cache_control_override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;private, max-age=600, stale-while-revalidate=300&lt;/span&gt;

  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authz_allow_if_authed_pii&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;opa&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;intercept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;response&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*slashid_config&lt;/span&gt;
        &lt;span class="na"&gt;policy_decision_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/authz/allow&lt;/span&gt;
        &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;package authz&lt;/span&gt;

          &lt;span class="s"&gt;import future.keywords.if&lt;/span&gt;

          &lt;span class="s"&gt;default allow := false&lt;/span&gt;

          &lt;span class="s"&gt;key_found(obj, key) if { obj[key] }&lt;/span&gt;

          &lt;span class="s"&gt;jwks_request := http.send({&lt;/span&gt;
              &lt;span class="s"&gt;"cache": true,&lt;/span&gt;
              &lt;span class="s"&gt;"method": "GET",&lt;/span&gt;
              &lt;span class="s"&gt;"url": "https://api.slashid.com/.well-known/jwks.json"&lt;/span&gt;
          &lt;span class="s"&gt;})&lt;/span&gt;
          &lt;span class="s"&gt;valid_signature if io.jwt.verify_rs256(input.request.token, jwks_request.raw_body)&lt;/span&gt;

          &lt;span class="s"&gt;allow if not key_found(input.response.http.headers, "X-Gate-Anonymize-1")&lt;/span&gt;
          &lt;span class="s"&gt;allow if valid_signature&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pii_anonymizer&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anonymizer&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;intercept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;request_response&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;anonymizers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;

          &lt;span class="s"&gt;DEFAULT:&lt;/span&gt;
            &lt;span class="s"&gt;type: keep&lt;/span&gt;
  &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*/get_user'&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://backend:8000&lt;/span&gt;
      &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;pii_anonymizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;authz_deny_pii&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule is a bit more complicated, let’s see what happens step by step.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;First, we retrieve the JSON Web Key Set (JWKS) from &lt;code&gt;https://api.slashid.com/.well-known/jwks.json&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Later, we check that either the incoming authorization token has a valid RS256 signature signed by SlashID or that &lt;code&gt;X-Gate-Anonymize-1&lt;/code&gt; is not present.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If either condition is true, the request is allowed. Let's see this in action:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s1"&gt;'http://gate:8080/api/get_user/?email=test@example.com'&lt;/span&gt; | jq
&lt;span class="k"&gt;*&lt;/span&gt; processing: http://gate:8080/api/get_user/?email&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;@example.com
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:--     0&lt;span class="k"&gt;*&lt;/span&gt;   Trying 172.27.0.5:8080...
&lt;span class="k"&gt;*&lt;/span&gt; Connected to gate &lt;span class="o"&gt;(&lt;/span&gt;172.27.0.5&lt;span class="o"&gt;)&lt;/span&gt; port 8080
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; GET /api/get_user/?email&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;@example.com HTTP/1.1
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Host: gate:8080
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; User-Agent: curl/8.2.1
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Accept: &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&amp;lt; HTTP/1.1 403 Forbidden
&amp;lt; Cache-Control: private, max-age&lt;span class="o"&gt;=&lt;/span&gt;600, stale-while-revalidate&lt;span class="o"&gt;=&lt;/span&gt;300
&amp;lt; Content-Length: 0
&amp;lt; Content-Type: application/json
&amp;lt; Date: Sat, 02 Sep 2023 16:04:24 GMT
&amp;lt; Server: uvicorn
&amp;lt; Via: 1.0 gate
&amp;lt; X-Gate-Anonymize-1: &lt;span class="nv"&gt;$.&lt;/span&gt;body.email 0 64 EMAIL_ADDRESS
&amp;lt;
  0     0    0     0    0     0      0      0 &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:--     0
&lt;span class="k"&gt;*&lt;/span&gt; Connection &lt;span class="c"&gt;#0 to host gate left intact&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request above is blocked because there is PII in the response and no valid JWT has been provided.&lt;/p&gt;

&lt;p&gt;Let's send a request with a valid token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;TOKEN&amp;gt;"&lt;/span&gt; &lt;span class="s1"&gt;'http://gate:8080/api/get_user/?email=test@example.com'&lt;/span&gt; | jq
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"email"&lt;/span&gt;: &lt;span class="s2"&gt;"test@example.com"&lt;/span&gt;,
  &lt;span class="s2"&gt;"id"&lt;/span&gt;: 1,
  &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"Test User"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note in this case that we configured the PII plugin to alert of PII presence but not to replace or obfuscate it in any way, hence why we see the original clear-text response.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Depending on the IdP you are using, it is also possible to create more complex policies that not only check the validity of the identity token, but also examine specific properties of the token.&lt;br&gt;
(Look out for our next Gate blogpost for a deeper dive into this topic!)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Blocking requests to unknown URLs
&lt;/h2&gt;

&lt;p&gt;More often than not, companies don't really know which APIs are exposed to begin with. Gate can help in this scenario too.&lt;/p&gt;

&lt;p&gt;Gate plugin instances can be applied to all routes, or you can select specific routes. In the example config below we enable the PII and OPA plugin instances on all routes and selectively disable them on specific routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
    &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;

  &lt;span class="na"&gt;plugins_http_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
      &lt;span class="na"&gt;cache_control_override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;private, max-age=600, stale-while-revalidate=300&lt;/span&gt;

  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authz_allow_if_authed_pii&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;opa&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;intercept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;response&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*slashid_config&lt;/span&gt;
        &lt;span class="na"&gt;policy_decision_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/authz/allow&lt;/span&gt;
        &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;package authz&lt;/span&gt;

          &lt;span class="s"&gt;import future.keywords.if&lt;/span&gt;

          &lt;span class="s"&gt;default allow := false&lt;/span&gt;

          &lt;span class="s"&gt;key_found(obj, key) if { obj[key] }&lt;/span&gt;

          &lt;span class="s"&gt;jwks_request := http.send({&lt;/span&gt;
              &lt;span class="s"&gt;"cache": true,&lt;/span&gt;
              &lt;span class="s"&gt;"method": "GET",&lt;/span&gt;
              &lt;span class="s"&gt;"url": "https://api.slashid.com/.well-known/jwks.json"&lt;/span&gt;
          &lt;span class="s"&gt;})&lt;/span&gt;
          &lt;span class="s"&gt;valid_signature if io.jwt.verify_rs256(input.request.token, jwks_request.raw_body)&lt;/span&gt;

          &lt;span class="s"&gt;allow if not key_found(input.response.http.headers, "X-Gate-Anonymize-1")&lt;/span&gt;
          &lt;span class="s"&gt;allow if valid_signature&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pii_anonymizer&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anonymizer&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;intercept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;request_response&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;anonymizers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;DEFAULT:&lt;/span&gt;
            &lt;span class="s"&gt;type: keep&lt;/span&gt;

  &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*/api/echo"&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://backend:8000&lt;/span&gt;
      &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;authz_allow_if_authed_pii&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;pii_anonymizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://backend:8000&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note how the plugins are defined as &lt;code&gt;enabled&lt;/code&gt; by default and how in the URLs we explicitly disable the plugins on selected paths (e.g. &lt;code&gt;"*/api/echo"&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/server/app &lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://gate:8080/api/echo'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"email=abc@abc.com"&lt;/span&gt; | jq
Note: Unnecessary use of &lt;span class="nt"&gt;-X&lt;/span&gt; or &lt;span class="nt"&gt;--request&lt;/span&gt;, POST is already inferred.
&lt;span class="k"&gt;*&lt;/span&gt; processing: http://gate:8080/api/echo
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:--     0&lt;span class="k"&gt;*&lt;/span&gt;   Trying 172.27.0.5:8080...
&lt;span class="k"&gt;*&lt;/span&gt; Connected to gate &lt;span class="o"&gt;(&lt;/span&gt;172.27.0.5&lt;span class="o"&gt;)&lt;/span&gt; port 8080
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; POST /api/echo HTTP/1.1
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Host: gate:8080
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; User-Agent: curl/8.2.1
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Accept: &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Content-Length: 17
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Content-Type: application/x-www-form-urlencoded
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;17 bytes data]
&amp;lt; HTTP/1.1 200 OK
&amp;lt; Cache-Control: private, max-age&lt;span class="o"&gt;=&lt;/span&gt;600, stale-while-revalidate&lt;span class="o"&gt;=&lt;/span&gt;300
&amp;lt; Content-Length: 360
&amp;lt; Content-Type: application/json
&amp;lt; Date: Sun, 03 Sep 2023 09:30:38 GMT
&amp;lt; Server: uvicorn
&amp;lt; Via: 1.0 gate
&amp;lt;
&lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;360 bytes data]
100   377  100   360  100    17  32933   1555 &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- 37700
&lt;span class="k"&gt;*&lt;/span&gt; Connection &lt;span class="c"&gt;#0 to host gate left intact&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"method"&lt;/span&gt;: &lt;span class="s2"&gt;"POST"&lt;/span&gt;,
  &lt;span class="s2"&gt;"headers"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"host"&lt;/span&gt;: &lt;span class="s2"&gt;"backend:8000"&lt;/span&gt;,
    &lt;span class="s2"&gt;"user-agent"&lt;/span&gt;: &lt;span class="s2"&gt;"curl/8.2.1"&lt;/span&gt;,
    &lt;span class="s2"&gt;"content-length"&lt;/span&gt;: &lt;span class="s2"&gt;"17"&lt;/span&gt;,
    &lt;span class="s2"&gt;"accept"&lt;/span&gt;: &lt;span class="s2"&gt;"*/*"&lt;/span&gt;,
    &lt;span class="s2"&gt;"content-type"&lt;/span&gt;: &lt;span class="s2"&gt;"application/x-www-form-urlencoded"&lt;/span&gt;,
    &lt;span class="s2"&gt;"x-b3-sampled"&lt;/span&gt;: &lt;span class="s2"&gt;"1"&lt;/span&gt;,
    &lt;span class="s2"&gt;"x-b3-spanid"&lt;/span&gt;: &lt;span class="s2"&gt;"39b9a26c103c6b5d"&lt;/span&gt;,
    &lt;span class="s2"&gt;"x-b3-traceid"&lt;/span&gt;: &lt;span class="s2"&gt;"ce0b56fc209ec47fbe0496606595c06b"&lt;/span&gt;,
    &lt;span class="s2"&gt;"accept-encoding"&lt;/span&gt;: &lt;span class="s2"&gt;"gzip"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"url"&lt;/span&gt;: &lt;span class="s2"&gt;"http://backend:8000/api/echo"&lt;/span&gt;,
  &lt;span class="s2"&gt;"body"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"email"&lt;/span&gt;: &lt;span class="s2"&gt;"abc@abc.com"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
/usr/server/app &lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="s1"&gt;'http://gate:8080/api/get_user/?email=test@example.com'&lt;/span&gt; | jq
&lt;span class="k"&gt;*&lt;/span&gt; processing: http://gate:8080/api/get_user/?email&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;@example.com
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:--     0&lt;span class="k"&gt;*&lt;/span&gt;   Trying 172.27.0.5:8080...
&lt;span class="k"&gt;*&lt;/span&gt; Connected to gate &lt;span class="o"&gt;(&lt;/span&gt;172.27.0.5&lt;span class="o"&gt;)&lt;/span&gt; port 8080
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; GET /api/get_user/?email&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;@example.com HTTP/1.1
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Host: gate:8080
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; User-Agent: curl/8.2.1
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Accept: &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&amp;lt; HTTP/1.1 403 Forbidden
&amp;lt; Cache-Control: private, max-age&lt;span class="o"&gt;=&lt;/span&gt;600, stale-while-revalidate&lt;span class="o"&gt;=&lt;/span&gt;300
&amp;lt; Content-Length: 0
&amp;lt; Content-Type: application/json
&amp;lt; Date: Sun, 03 Sep 2023 09:31:37 GMT
&amp;lt; Server: uvicorn
&amp;lt; Via: 1.0 gate
&amp;lt; X-Gate-Anonymize-1: &lt;span class="nv"&gt;$.&lt;/span&gt;body.email 0 16 EMAIL_ADDRESS
&amp;lt;
  0     0    0     0    0     0      0      0 &lt;span class="nt"&gt;--&lt;/span&gt;:--:--  0:00:01 &lt;span class="nt"&gt;--&lt;/span&gt;:--:--     0
&lt;span class="k"&gt;*&lt;/span&gt; Connection &lt;span class="c"&gt;#0 to host gate left intact&lt;/span&gt;
/usr/server/app &lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running in monitoring mode
&lt;/h2&gt;

&lt;p&gt;Just like the PII detection plugin, the OPA plugin also supports monitoring mode by adding &lt;code&gt;monitoring_mode: true&lt;/code&gt; in its parameters as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authz_allow_if_authed_pii&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;opa&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;intercept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;response&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*slashid_config&lt;/span&gt;
        &lt;span class="na"&gt;monitoring_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;policy_decision_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/authz/allow&lt;/span&gt;
        &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;package authz&lt;/span&gt;

          &lt;span class="s"&gt;import future.keywords.if&lt;/span&gt;

          &lt;span class="s"&gt;default allow := false&lt;/span&gt;

          &lt;span class="s"&gt;key_found(obj, key) if { obj[key] }&lt;/span&gt;

          &lt;span class="s"&gt;jwks_request := http.send({&lt;/span&gt;
              &lt;span class="s"&gt;"cache": true,&lt;/span&gt;
              &lt;span class="s"&gt;"method": "GET",&lt;/span&gt;
              &lt;span class="s"&gt;"url": "https://api.slashid.com/.well-known/jwks.json"&lt;/span&gt;
          &lt;span class="s"&gt;})&lt;/span&gt;
          &lt;span class="s"&gt;valid_signature if io.jwt.verify_rs256(input.request.token, jwks_request.raw_body)&lt;/span&gt;

          &lt;span class="s"&gt;allow if not key_found(input.response.http.headers, "X-Gate-Anonymize-1")&lt;/span&gt;
          &lt;span class="s"&gt;allow if valid_signature&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's send a request with an invalid token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer abc"&lt;/span&gt; &lt;span class="s1"&gt;'http://gate:8080/api/get_user/?email=test@example.com'&lt;/span&gt; | jq
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"email"&lt;/span&gt;: &lt;span class="s2"&gt;"test@example.com"&lt;/span&gt;,
  &lt;span class="s2"&gt;"id"&lt;/span&gt;: 1,
  &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"Test User"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request passes but Gate logs the policy violation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;gate-demo-gate&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;time=&lt;/span&gt;&lt;span class="mi"&gt;2023-09-04&lt;/span&gt;&lt;span class="err"&gt;T&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;06&lt;/span&gt;&lt;span class="err"&gt;Z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;level=info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;msg=OPA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;decision:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;decision_id=d&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="err"&gt;d-da&lt;/span&gt;&lt;span class="mi"&gt;43-4786&lt;/span&gt;&lt;span class="err"&gt;-ae&lt;/span&gt;&lt;span class="mi"&gt;15-1&lt;/span&gt;&lt;span class="err"&gt;ec&lt;/span&gt;&lt;span class="mi"&gt;91199786&lt;/span&gt;&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;decision_provenance=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="err"&gt;fc&lt;/span&gt;&lt;span class="mi"&gt;439&lt;/span&gt;&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="err"&gt;c&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="mi"&gt;667&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="mi"&gt;128606390&lt;/span&gt;&lt;span class="err"&gt;ad&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;cb&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="err"&gt;ded&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="err"&gt;-dirty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2023-09-02&lt;/span&gt;&lt;span class="err"&gt;T&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="err"&gt;Z&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;gate:&lt;/span&gt;&lt;span class="p"&gt;{}]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;plugin=opa&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;req_path=/api/get_user/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;request_host=gate:&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;request_url=/api/get_user/?email=test%&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="err"&gt;example.com&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Performance is key when intercepting and modifying network traffic, our plugins were built for high performance in mind.&lt;br&gt;
For instance we embed an optimized version a rego interpreter vs standing up a separate OPA server.&lt;/p&gt;

&lt;p&gt;Let's look at a simple benchmark to see the impact of the two plugins on the network traffic.&lt;/p&gt;

&lt;p&gt;Here's a simple benchmarking script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nv"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Running &lt;/span&gt;&lt;span class="nv"&gt;$iterations&lt;/span&gt;&lt;span class="s2"&gt; iterations for curl &lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;totaltime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0

&lt;span class="k"&gt;for &lt;/span&gt;run &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 &lt;span class="nv"&gt;$iterations&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;do
 &lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{time_total}"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
 &lt;span class="nv"&gt;totaltime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$totaltime&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; + &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$time&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | bc&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nv"&gt;avgtimeMs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"scale=4; 1000*&lt;/span&gt;&lt;span class="nv"&gt;$totaltime&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$iterations&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | bc&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Averaged &lt;/span&gt;&lt;span class="nv"&gt;$avgtimeMs&lt;/span&gt;&lt;span class="s2"&gt; ms in &lt;/span&gt;&lt;span class="nv"&gt;$iterations&lt;/span&gt;&lt;span class="s2"&gt; iterations"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our demo, a request without any interception results in the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/server/app &lt;span class="nv"&gt;$ &lt;/span&gt;./benchmark.sh 10000 &lt;span class="s1"&gt;'http://gate:8080/api/get_user/?email=test@example.com'&lt;/span&gt;
Running 10000 iterations &lt;span class="k"&gt;for &lt;/span&gt;curl http://gate:8080/api/get_user/?email&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;@example.com
Averaged 1.1820 ms &lt;span class="k"&gt;in &lt;/span&gt;10000 iterations
/usr/server/app &lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we enable PII detection and rewriting (hashing of the email address) coupled with our caching plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/server/app &lt;span class="nv"&gt;$ &lt;/span&gt;./benchmark.sh 10000 &lt;span class="s1"&gt;'http://gate:8080/api/get_user/?email=test@example.com'&lt;/span&gt;
Running 10000 iterations &lt;span class="k"&gt;for &lt;/span&gt;curl http://gate:8080/api/get_user/?email&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;@example.com
Averaged 1.5955 ms &lt;span class="k"&gt;in &lt;/span&gt;10000 iterations
/usr/server/app &lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we test PII detection in monitoring mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/server/app &lt;span class="nv"&gt;$ &lt;/span&gt;./benchmark.sh 10000 &lt;span class="s1"&gt;'http://gate:8080/api/get_user/?email=test@example.com'&lt;/span&gt;
Running 10000 iterations &lt;span class="k"&gt;for &lt;/span&gt;curl http://gate:8080/api/get_user/?email&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;@example.com
Averaged 1.5176 ms &lt;span class="k"&gt;in &lt;/span&gt;10000 iterations
/usr/server/app &lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last, let's run PII detection in monitoring mode coupled with OPA like we did in the example in the previous section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/server/app &lt;span class="nv"&gt;$ &lt;/span&gt;./benchmark.sh 10000 &lt;span class="s1"&gt;'http://gate:8080/api/get_user/?email=test@example.com'&lt;/span&gt;
Running 10000 iterations &lt;span class="k"&gt;for &lt;/span&gt;curl http://gate:8080/api/get_user/?email&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;@example.com
Averaged 1.8532 ms &lt;span class="k"&gt;in &lt;/span&gt;10000 iterations
/usr/server/app &lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks to a combination of our caching plugin and Gate’s own architecture, the average overhead in our toy application is 0.6712 ms when both OPA and PII detections are turned on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this blogpost we've shown how you can combine the Gate PII and OPA plugins to easily detect and prevent PII leakage.&lt;/p&gt;

&lt;p&gt;We’d love to hear any feedback you may have! Try out Gate with a &lt;a href="https://console.slashid.dev/signup/gate"&gt;free account&lt;/a&gt;. If you'd like to use the PII detection plugin, please contact us at &lt;code&gt;contact@slashid.dev&lt;/code&gt;!&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>api</category>
      <category>apigateway</category>
    </item>
    <item>
      <title>Authenticate your Shopify customers with SlashID</title>
      <dc:creator>vincenzoiozzo</dc:creator>
      <pubDate>Thu, 27 Jul 2023 10:33:59 +0000</pubDate>
      <link>https://forem.com/vincenzoiozzo/authenticate-your-shopify-customers-with-slashid-38f0</link>
      <guid>https://forem.com/vincenzoiozzo/authenticate-your-shopify-customers-with-slashid-38f0</guid>
      <description>&lt;h2&gt;
  
  
  Drive repeat business, reduce friction
&lt;/h2&gt;

&lt;p&gt;Successfully identifying your customers is key to repeat business, personalizing shopping experiences, and effective multi-channel marketing.&lt;/p&gt;

&lt;p&gt;However, complex checkouts and password requirements are two of the top reasons for increased cart abandonment rate.&lt;/p&gt;

&lt;p&gt;Simplifying the checkout process and using passwordless login can retain customer data for personalization and reduce cart abandonment, improving the user experience and boosting account creation conversion rates &lt;a href="https://www.cio.com/article/234915/ditching-passwords-and-increasing-ecommerce-conversion-rates-by-54-2.html"&gt;by up to 54%&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By default, Shopify provides a login form where your customers can sign up or sign in using email and password. It is not possible to easily integrate users across multiple channels or platforms (e.g., your own marketing website).&lt;/p&gt;

&lt;p&gt;By adding SlashID Login to your Shopify store, your customers will be able to login to your store without having to create and remember passwords for an effortless shopping experience. We support all passwordless authentication methods like passkeys, social login, SMS and email links.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control UX and branding
&lt;/h2&gt;

&lt;p&gt;The SlashID login form is entirely configurable so you can choose which authentication methods to enable for your customers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KVdapgkH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/shopify-login-app/configuration.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KVdapgkH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/shopify-login-app/configuration.png" alt="Configuration" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, you can customize the look and feel of the SlashID login form directly from the Shopify admin panel. This includes setting the color palette, logo, font family and text content to match your brand.&lt;/p&gt;

&lt;p&gt;Try out the SlashID Login app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get a free SlashID account from our &lt;a href="https://www.slashid.dev/"&gt;website&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://apps.shopify.com/slashid-login"&gt;SlashID Login app&lt;/a&gt; from the official Shopify store and configure it using our step by step guide&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FcQji2E---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/shopify-login-app/customization.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FcQji2E---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/shopify-login-app/customization.png" alt="Customization" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One login and user base across all your channels and products
&lt;/h2&gt;

&lt;p&gt;The SlashID Login app is built on top of SlashID user tokens, which are exchanged for &lt;a href="https://shopify.dev/docs/api/multipass"&gt;Shopify Multipass tokens&lt;/a&gt; in the background. This allows you to use SlashID’s login across all your platforms, storefronts and webapps, so your customers can navigate between your own websites and your Shopify store without having to authenticate multiple times.&lt;/p&gt;

&lt;p&gt;Without the hassle of repeated logins, your customers will benefit from the enhanced user experience with seamless transitions and continuity across different systems. It improves convenience, saves time, and reduces friction, ultimately resulting in higher customer satisfaction and engagement while maintaining a unified brand experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add workflows and organizations to your Store
&lt;/h2&gt;

&lt;p&gt;Through the SlashID Login App you can leverage modern features like &lt;a href="https://developer.slashid.dev/docs/guides/webhooks"&gt;Webhook&lt;/a&gt; and &lt;a href="https://developer.slashid.dev/docs/guides/suborgs"&gt;Organizations&lt;/a&gt; to customize the user journey, create complex users structures and orchestrate analytics and marketing workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out!
&lt;/h2&gt;

&lt;p&gt;Give your customers the smoothest login experience with the SlashID Login app. Eliminate account recovery friction with passkeys and social logins, customize the SlashID login form with your branding and allow seamless transitions between your own platform and Shopify.&lt;/p&gt;

&lt;p&gt;Ready to get started with SlashID? Get a free account &lt;a href="https://console.slashid.dev/signup"&gt;here&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Is there a feature you’d like to see, or have you tried out the SlashID Login app and have some feedback? &lt;a href="//mailto:contact@slashid.dev"&gt;Let us know&lt;/a&gt;!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Passkeys deep-dive: security and implementations considerations</title>
      <dc:creator>vincenzoiozzo</dc:creator>
      <pubDate>Fri, 02 Jun 2023 23:30:49 +0000</pubDate>
      <link>https://forem.com/vincenzoiozzo/passkeys-deep-dive-security-and-implementations-considerations-557c</link>
      <guid>https://forem.com/vincenzoiozzo/passkeys-deep-dive-security-and-implementations-considerations-557c</guid>
      <description>&lt;p&gt;Since Google announced support for passkeys for all Gmail accounts – a clear first step in phasing out passwords – passkeys have been a hot topic.&lt;br&gt;
But what are passkeys exactly, how do they work and how are they going to change the security landscape? In this blog post, we review the current state of the technology from a security standpoint and we’ll discuss some critical aspects of passkey implementation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can also jump straight to the code here: &lt;a href="https://github.com/slashid/website-passkeys-playground"&gt;GitHub&lt;/a&gt; and &lt;a href="https://passkeys-playground.slashid.dev/"&gt;live&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Table of content
&lt;/h2&gt;

&lt;p&gt;This is a rather long blogpost, we split it in separate sections to make it easier to consume it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Passkeys 101&lt;/li&gt;
&lt;li&gt;Security and threat model&lt;/li&gt;
&lt;li&gt;Implementation considerations&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Passkeys 101
&lt;/h2&gt;

&lt;p&gt;Feel free to skip this section if you are already familiar with passkeys!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In this section&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are passkeys?&lt;/li&gt;
&lt;li&gt;Why now?&lt;/li&gt;
&lt;li&gt;How are credentials shared and stored?&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  What are passkeys?
&lt;/h3&gt;

&lt;p&gt;The big promise of passkeys is to get rid of passwords, of most phishing attacks and, in some cases, of multi factor authentication (MFA).&lt;/p&gt;

&lt;p&gt;A passkey is a commercial term for discoverable FIDO/WebAuthn credentials that can be used for passwordless authentication. From a technical point of view, a passkey is a public and private key pair that can be used to authenticate a user to a relying party (i.e., a website or an app) without using passwords.&lt;br&gt;
Websites can create or verify credentials through a web API called WebAuthn, implemented in all major browsers. &lt;br&gt;&lt;br&gt;
The keys are stored in an authenticator (this is an approximation, we’ll discuss more below), which could be anything from a physical security key to a smartphone’s secure enclave or even a virtual, software-based system that implements the Client to Authenticator Protocol 2 (CTAP2). CTAP2 is how an authenticator talks to a web browser to complete a WebAuthn credential creation or verification process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In other words, the browser talks to the authenticator through the CTAP2 protocol to perform the key creation and verification ceremonies initiated by a website through the WebAuthn APIs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Why now?
&lt;/h3&gt;

&lt;p&gt;The idea of using public-key cryptography for user authentication and the WebAuthn standard have been around for a number of years. However, until recently, there was no established infrastructure to share key pairs across devices, making account recovery and cross-device authentication hard. Due to these reasons, until now, the adoption of WebAuthn has been fairly modest, with it primarily implemented as a second authentication factor, not the primary one.&lt;/p&gt;

&lt;p&gt;Furthermore, the UX in most browsers was very counterintuitive until recently.&lt;/p&gt;

&lt;p&gt;Apple and Google's introduction of passkeys, coupled with their push to build ways to share credentials across devices, has significantly boosted interest in WebAuthn over the past 12 months. &lt;br&gt; For example, Apple &lt;a href="https://developer.apple.com/news/?id=mgdnfp8w"&gt;reported&lt;/a&gt; that Kayak, Robinhood and Instacart are all experimenting with it, and the recent passkey support for Gmail is the first large-scale rollout of the technology and will undoubtedly lead to significant improvements of the user experience.&lt;/p&gt;
&lt;h3&gt;
  
  
  How are credentials shared and stored?
&lt;/h3&gt;

&lt;p&gt;As mentioned above, passkeys are key pairs, and the private key is not supposed to be accessible to the relying party or the outside world.&lt;br&gt;
According to the WebAuthn standards, there are two types of credentials:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.w3.org/TR/webauthn/#discoverable-credential"&gt;Discoverable credentials&lt;/a&gt;: stored on the client device, meaning that the private key itself is stored in the persistent storage embedded in the authenticator&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.w3.org/TR/webauthn/#server-side-credential"&gt;Server-side credentials&lt;/a&gt;: The private key is encrypted with a second private key stored in the authenticator. The resulting blob is used as the credential ID for the key pair. There are multiple ways to achieve this; the most common one is called &lt;a href="https://en.wikipedia.org/wiki/Key_wrap"&gt;key wrapping&lt;/a&gt;. Note that while the relying party does have access to the private key, the relying party won’t be able to access it without the access to the authenticator, providing security guarantees similar to discoverable credentials&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Passkeys add a twist to this concept as they are discoverable credentials that can be exported from the authenticator. &lt;br&gt;&lt;/p&gt;

&lt;p&gt;It is critical to notice that while passkeys will reduce adoption friction for WebAuthn, the security guarantees of passkeys are lower than regular, non-exportable, WebAuthn credentials and they are not standardized. In fact, the &lt;a href="https://www.w3.org/TR/webauthn/#sctn-credential-loss-key-mobility"&gt;WebAuthn standard&lt;/a&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“This specification defines no protocol for backing up credential private keys, or for sharing them between authenticators. In general, it is expected that a credential private key never leaves the authenticator that created it. Losing an authenticator therefore, in general, means losing all credentials bound to the lost authenticator, which could lock the user out of an account if the user has only one credential registered with the Relying Party. Instead of backing up or sharing private keys, the Web Authentication API allows registering multiple credentials for the same user.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since there are no technical standards for passkeys, different providers synchronize credentials in various ways, and these details are often not disclosed publicly.&lt;br&gt;
In a previous &lt;a href="https://www.slashid.dev/blog/passkeys-deepdive/"&gt;blog post&lt;/a&gt;, we analyzed how Apple synchronizes passkeys. &lt;br&gt;&lt;/p&gt;

&lt;p&gt;As for Google, they use the Google Password/Passkey Manager, the security of which is beyond the scope of this blogpost, but you can read more &lt;a href="https://security.googleblog.com/2022/10/SecurityofPasskeysintheGooglePasswordManager.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A practical consequence of this arrangement is that the security of passkeys and account recovery heavily depends on the security of the account linked to the syncing service (often your Apple or Google account). While at first this may seem a scary proposition for many, most account recovery flows today offer no better security by simply resorting to a reset link sent to an email address.&lt;/p&gt;
&lt;h2&gt;
  
  
  Security and Threat-modeling
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In this section&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phishing&lt;/li&gt;
&lt;li&gt;Stolen Credentials&lt;/li&gt;
&lt;li&gt;The end of multi-factor authentication (MFA)?&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Phishing
&lt;/h3&gt;

&lt;p&gt;One of the key selling points of passkeys and WebAuthn is that they are safer than passwords, providing a strong protection against phishing. In fact, all WebAuthn compliant browsers enforce a number of security checks.&lt;/p&gt;

&lt;p&gt;First of all, WebAuthn doesn’t work without TLS. Furthermore, browsers enforce origin checks for credentials and most will prevent access to the platform authenticator unless the window is in focus or, in the case of Safari, the user triggers an action.&lt;/p&gt;

&lt;p&gt;Thanks to origin checks, credentials are bound to an origin and cannot be used on a different domain. For this reason, most of the recent attacks involving domain squatting and phishing would fall flat because the malicious site wouldn’t be able to initiate the WebAuthn authentication process.&lt;/p&gt;

&lt;p&gt;In other words, an attacker trying to compromise user credentials will need to either find a cross-site scripting (XSS) bug in the target website or a vulnerability in the browser, both of which are very high barriers to overcome. These are the only way in which they can bypass the WebAuthn checks, and even then a successful attacker wouldn’t have access to the private key itself, but only to a session token/cookie, which will expire once the browsing session is over.&lt;/p&gt;

&lt;p&gt;In this &lt;a href="https://www.slashid.dev/blog/webauthn-antiphishing/"&gt;blog post&lt;/a&gt;, we show the technical details of how Chrome enforces such checks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In comparison to all other authentication methods — where an attacker only has to register a seemingly related domain and have a similar looking webpage to fool the victim through phishing — it is clear that WebAuthn is a much safer authentication method.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Stolen Credentials
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Server-side
&lt;/h4&gt;

&lt;p&gt;Another benefit of passkeys is that even if the credential database of a relying party is compromised and credentials are leaked, an attacker won’t be able to exploit them because no clear-text private key is ever shared with the relying party.&lt;br&gt;
This also reduces the risk of dictionary attacks against reused credentials.&lt;/p&gt;
&lt;h4&gt;
  
  
  Client-side
&lt;/h4&gt;

&lt;p&gt;However, the private keys are now stored on the user device and their security and threat model is not always straightforward to assess. Note that even for server-side credentials, the key used to derive the private keys is stored on the authenticator, and so the threat model is roughly the same.&lt;/p&gt;

&lt;p&gt;As mentioned in the introduction, an authentication ceremony involves a website using the WebAuthn APIs to talk to the client/browser, which will interact with an authenticator via the CTAP2 protocol.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;However, no storage security guarantees are enforced on the authenticator. In particular, the location where keys are stored is highly dependent on the user device, operating system and whether hardware security keys are used.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We ranked the different solutions currently available from strongest to weakest security guarantees:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hardware security keys (Yubikeys or similar): these are dedicated devices with hardware security modules that only hold keys&lt;/li&gt;
&lt;li&gt;Modern iOS/Android devices: most modern high end mobile devices have a Secure Element chip that offers similar security guarantees to a hardware security key, in that even if the device is compromised the keys won’t be leaked&lt;/li&gt;
&lt;li&gt;Modern desktop devices: most modern laptops have a security module. However, certain hardware vendors limit which browsers can make use of the secure element&lt;/li&gt;
&lt;li&gt;Legacy mobile devices and desktops: private keys are normally stored in the user filesystems and while they might be encrypted, compromising the user device will most definitely also compromise the credentials.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;As a result, applications that require high security guarantees should assess the risk of storing credentials on unsafe, legacy devices. That said, those devices are also vulnerable to keyloggers which would compromise any password, so even an authenticator with lower security guarantees is in most cases better than passwords.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Adding credentials
&lt;/h3&gt;

&lt;p&gt;Since stealing credentials is not a feasible attack pattern with passkeys, attackers might be more interested in finding logic vulnerabilities that allow them to add their own credentials to a user account. By adding credentials to an account, the attacker achieves the same effect (i.e., they are able to login and impersonate the user) as stealing credentials.&lt;/p&gt;

&lt;p&gt;The WebAuthn standard doesn’t specify how a relying party should account for multiple credentials, hence it is left as a task for the developer. It does nevertheless encourage the registration of &lt;a href="https://www.w3.org/TR/webauthn/#sctn-credential-loss-key-mobility"&gt;multiple credentials&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Different websites could opt for different approaches here, one is to only allow one credential per account (similar to a password) and outsource the credential recovery process to Google/Apple/Passkey synchronization provider. Another one is to have a step-up authentication mechanism that allows a user to add another credential to their account after a further identity verification on their identity.&lt;/p&gt;
&lt;h3&gt;
  
  
  The end of multi-factor authentication (MFA)?
&lt;/h3&gt;

&lt;p&gt;The principle behind MFA is to increase the confidence in the user identity by using more than a single authentication factor. Generally, authentication factors can be one of three types: something you know, something you are or something you have.&lt;/p&gt;

&lt;p&gt;Some authenticators such as modern mobile phones and security keys can enforce two or more factors at the same time when accessing passkeys. For instance, on iOS you are required to use FaceID (something you are) or enter your pin (something you know) in order to login with a passkey (something you have).&lt;/p&gt;

&lt;p&gt;In certain jurisdictions, passkeys on modern authenticators could alone satisfy the EU Secure Customer Authentication standards (more on this &lt;a href="https://www.slashid.dev/blog/webauthn-compliance/"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In general, non-roaming WebAuthn credentials stored on dedicated hardware security keys with biometrics/pin support should be safe as a replacement for MFA.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whether passkeys could be a replacement for MFA is dependent on the trust the application has in the recovery process of the vendor synchronizing the credentials (in most cases, Google or Apple), because ultimately passkeys will be synchronized across multiple devices and could be recovered through the account recovery process of the vendors.&lt;/p&gt;

&lt;p&gt;We think that, for the time being, MFA will likely still remain a key defense against attackers for high-security applications. However, for lower security applications, MFA could be entirely replaced by passkeys on modern devices within a few years.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation considerations
&lt;/h2&gt;

&lt;p&gt;Now that we’ve reviewed the UX and security implications of passkeys, let’s discuss key implementation details that a relying party should bear in mind when adopting passkeys.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In this section&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure key generation&lt;/li&gt;
&lt;li&gt;Secure login&lt;/li&gt;
&lt;li&gt;Random nonces&lt;/li&gt;
&lt;li&gt;Security flags&lt;/li&gt;
&lt;li&gt;Discoverable credentials vs server-side credentials&lt;/li&gt;
&lt;li&gt;Adding new credentials to an account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebAuthn is a fairly complicated technology; we recommend not rolling out your own implementation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Secure key generation
&lt;/h3&gt;

&lt;p&gt;The process of creating a credential starts when the relying party sends a request with a number of parameters (full list &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create"&gt;here&lt;/a&gt;). At a minimum, the server must send a random challenge/nonce, like shows in this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="n"&gt;user_cred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="s"&gt;'user_name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'example@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s"&gt;'display_name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Mr Example’,
   '&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="s"&gt;': os.urandom(12),
}
challenge = os.urandom(32)
…
return jsonify({
    '&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="s"&gt;': None,
    '&lt;/span&gt;&lt;span class="n"&gt;displayName&lt;/span&gt;&lt;span class="s"&gt;': user_cred['&lt;/span&gt;&lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="s"&gt;'],
    '&lt;/span&gt;&lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="s"&gt;': user_cred['&lt;/span&gt;&lt;span class="n"&gt;user_name&lt;/span&gt;&lt;span class="s"&gt;'],
    '&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="s"&gt;': base64.b64encode(user_cred['&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="s"&gt;']).decode('&lt;/span&gt;&lt;span class="nb"&gt;ascii&lt;/span&gt;&lt;span class="s"&gt;'),
    '&lt;/span&gt;&lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="s"&gt;': base64.b64encode(challenge).decode('&lt;/span&gt;&lt;span class="nb"&gt;ascii&lt;/span&gt;&lt;span class="s"&gt;'),
    '&lt;/span&gt;&lt;span class="n"&gt;relyingPartyName&lt;/span&gt;&lt;span class="s"&gt;': '&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="s"&gt;'}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The web application will then call &lt;code&gt;navigator.credentials.create&lt;/code&gt; to generate a key pair.&lt;br&gt;
The parameters of the call will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;   &lt;span class="nx"&gt;credOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// Relying party&lt;/span&gt;
       &lt;span class="na"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;relyingPartyName&lt;/span&gt; &lt;span class="c1"&gt;// 'localhost'&lt;/span&gt;
       &lt;span class="p"&gt;},&lt;/span&gt;


       &lt;span class="c1"&gt;// User parameters&lt;/span&gt;
       &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userIdBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// os.urandom(12)&lt;/span&gt;
         &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;//example@example.com&lt;/span&gt;
         &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;//Mr Example&lt;/span&gt;
       &lt;span class="p"&gt;},&lt;/span&gt;


       &lt;span class="c1"&gt;// This specifies the list of acceptable key types for the authenticator to generate.&lt;/span&gt;
       &lt;span class="c1"&gt;// For most implementations, it’s either ECC (-7) or RSA (-256).&lt;/span&gt;
       &lt;span class="na"&gt;pubKeyCredParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
         &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="na"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// ECC&lt;/span&gt;
       &lt;span class="p"&gt;}],&lt;/span&gt;


       &lt;span class="c1"&gt;// Here we can decide whether to use the current device as the authenticator or whether&lt;/span&gt;
       &lt;span class="c1"&gt;// we should allow the user to select the authenticator (eg: an external hardware security key)&lt;/span&gt;
       &lt;span class="c1"&gt;// ‘platform’ forces the choice of the local device&lt;/span&gt;
       &lt;span class="na"&gt;authenticatorSelection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="na"&gt;authenticatorAttachment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;platform&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;},&lt;/span&gt;


       &lt;span class="c1"&gt;// This is to avoid replay attacks and needs to be random. It’s the nonce sent by the server&lt;/span&gt;
       &lt;span class="na"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;challengeBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;


       &lt;span class="c1"&gt;// Ask the authenticator to generate an attestation statement to prove the device type used&lt;/span&gt;
       &lt;span class="c1"&gt;// to generate this key. This doesn’t always work, apple devices will refuse to generate an&lt;/span&gt;
       &lt;span class="c1"&gt;// attestation statement for instance.&lt;/span&gt;
       &lt;span class="na"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;direct&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the key pair has been generated, the public key is returned to the relying party together with a number of other fields. The relying party verifies the data and, if successful, stores the public key associated with that user. This is an example payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attestation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEgwRgIhAM19ZXaMJ703tCUuinx9Pqkh+hhKAh0N+nZYufV0SSX1AiEAxxOaRaVchuQDFn8FWnfrR9lbLPR7fHzTlJktbvra5x9oYXV0aERhdGFYpEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjRQAAAACtzgACNbzGCmSLCyXx8FUDACCaZeUU5GyTWfAlwU+D8vq/UsVgjwH1RAt8G6ngG/pyF6UBAgMmIAEhWCBOVpnWqcLp0U6DyQe1roMkSBrTRcir+LcIP1Pa925OhSJYIE+275/X4cNt16Q4hOYj5HN/Qb0vKaEH4p7jtsHfxvJd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"clientData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiczBqMFVjTU4tVV8zcGdZLTFzeXNqVXhySEVxREJGWmQ2a3RVNnZoeVh0dyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mmXlFORsk1nwJcFPg_L6v1LFYI8B9UQLfBup4Bv6chc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;needs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;persisted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;associated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;user&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public-key"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;clientData&lt;/code&gt; object is a Base64-encode JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webauthn.create"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"challenge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s0j0UcMN-U_3pgY-1sysjUxrHEqDBFZd6ktU6vhyXtw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:5000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"crossOrigin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://www.w3.org/TR/webauthn/#attestation-object"&gt;attestation object&lt;/a&gt; is a Base64-encode &lt;a href="https://en.wikipedia.org/wiki/CBOR"&gt;CBOR&lt;/a&gt; map. The shape of the attestation object is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"attStmt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;'alg':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;'sig':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"0E&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02R&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a4&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;fa&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f0&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;b9@&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ed&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;c3&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;eb&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f8&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;0f"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;d2?*p&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;dd&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;9b&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;11W&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;eb&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;dd&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f0&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;08&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;cb4&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;16"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a0&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;88&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ce&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02!&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;b4S&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;07&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;c8pJ&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;b9=="&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;08&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e1Qf&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;08&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;959O&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;8a/H7&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;13&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ec&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;9e&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;cf&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;89&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ca&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ca&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;fb"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"authData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"I&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;96&lt;/span&gt;&lt;span class="se"&gt;\r\x&lt;/span&gt;&lt;span class="s2"&gt;e5&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;88&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;0e&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;8cht4&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;17&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;0fdv`[&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;8f&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e4&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ae&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;b9"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a2&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;862&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;c7&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;99&lt;/span&gt;&lt;span class="se"&gt;\\\x&lt;/span&gt;&lt;span class="s2"&gt;f3&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ba&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;83&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;1d&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;97cE&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ad&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ce&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;025&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;bc&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;c6&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;d&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;8b&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;0b%&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f1&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f0U"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;03&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00 &lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e2&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;98&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;b2&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;d8&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a5QK&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;84&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;87&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;83/Z"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;af&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a3&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f0E&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e1J&lt;/span&gt;&lt;span class="se"&gt;\n\x&lt;/span&gt;&lt;span class="s2"&gt;99&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e7&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;d9&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;1cR&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;9eE!&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;eb&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;{i&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a5"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;01&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;03&amp;amp; &lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;01!X &lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;bel&amp;gt;^%&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;90&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;81U&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;b7&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e1&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a9&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;da&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;19r"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;9c&amp;lt;4M&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;9c&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;1dkZ&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e8&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;8d&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;fb&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;dd /&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;17&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e7X j&amp;amp;w&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;c7"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f4&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;18C&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;8ec&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;1e&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;95&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a7&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;1e&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;18&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f9D&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;b0["&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;de&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;11&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;912&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f4&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;b8&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;abX&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e0&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;c4&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;0eU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fmt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'packed'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The decoded &lt;code&gt;authData&lt;/code&gt; (which is a variable-length byte array, full spec &lt;a href="https://www.w3.org/TR/webauthn/#authenticator-data"&gt;here&lt;/a&gt;) in the &lt;code&gt;attestation&lt;/code&gt; field gives us information about the credential just created and certain security flags we’ll discuss later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"attestedCredData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nl"&gt;"aaguid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ad&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ce&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;025&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;bc&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;c6&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;d&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;8b&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;0b%"&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f1&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f0U"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"credentialId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;19&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;81&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e5&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;989Dq]"&lt;/span&gt;&lt;span class="w"&gt;
                             &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;0b&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f5&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;c6&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;bc]Do&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;bb&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;d9-~&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ac"&lt;/span&gt;&lt;span class="w"&gt;
                             &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;81&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;8dgS&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ea&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;08S&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;10?&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;124&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;95"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"credentialIdLength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"credentialPublicKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                       &lt;/span&gt;&lt;span class="nl"&gt;"eccurve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                       &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;cryptography.hazmat.backends.openssl.ec._EllipticCurvePublicKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;x&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="err"&gt;f&lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                       &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ECP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                       &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;":&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e2&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;0f}"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;8a&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;d2$"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a2&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;cc{."&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"l&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a4&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f7&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;bf"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e8:&lt;/span&gt;&lt;span class="se"&gt;\\\x&lt;/span&gt;&lt;span class="s2"&gt;18"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;06&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;cbC&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;11"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"K&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;beG&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e5"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;0fL&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                       &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"c&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a1&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;96@"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;e&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a1&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ac"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;cfB&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a4&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;99"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\\x&lt;/span&gt;&lt;span class="s2"&gt;01&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;c0"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e8&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ce&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;9e^"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"#p&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;07&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;c3"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;86&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;07&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;04s"&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;18P&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a8"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"flags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"attestedCredDataIncluded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"extensionDataIncluded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"userPresent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"userVerified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;True&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"flagsRaw"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;69&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"rpIdHash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"I&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;96&lt;/span&gt;&lt;span class="se"&gt;\r\x&lt;/span&gt;&lt;span class="s2"&gt;e5&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;88&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;0e&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;8cht4&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;17&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;0fdv`[&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;8f&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;e4&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ae&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;b9"&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;a2&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;862&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;c7&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;99&lt;/span&gt;&lt;span class="se"&gt;\\\x&lt;/span&gt;&lt;span class="s2"&gt;f3&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;ba&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;83&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;1d&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;97"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"signCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relying party must carefully verify the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The challenge in &lt;code&gt;clientData&lt;/code&gt; corresponds to the one the relying party sent at the beginning of the key creation ceremony.&lt;/li&gt;
&lt;li&gt;The blob contains the sha256 of the relying party id (rpId), in this example “localhost”. The relying party should verify that the hash matches the expected rpId.&lt;/li&gt;
&lt;li&gt;Verify that &lt;code&gt;attStmt&lt;/code&gt; within the &lt;code&gt;attestation&lt;/code&gt; object is correct. In particular, each type of attestation statement has a different format and way to verify its validity. The attestation statement is key to assess the properties of the authenticator and its trustworthiness. Here’s more information on how to verify the statement based on their &lt;a href="https://www.w3.org/TR/webauthn/#verification-procedure"&gt;type&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Starting from iOS 16, Safari won’t generate attestation statements (&lt;code&gt;attStmt&lt;/code&gt; set to &lt;code&gt;none&lt;/code&gt;) for platform keys so a relying party won’t be able to verify the provenance of webauthn keys generated on the device.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure login
&lt;/h3&gt;

&lt;p&gt;The high-level process to verify a login attempt is for the relying party to generate a random challenge and for the client to sign that challenge with a key such that the server can verify the signature on the challenge by possessing the public key associated with the keypair - this is called an assertion.&lt;/p&gt;

&lt;p&gt;In other words, the relying party verifies an assertion made by the client by verifying the signature on the random nonce against a public key that was registered for that account.&lt;/p&gt;

&lt;p&gt;In practice there are several steps that a relying party needs to perform in order to verify the assertion correctly.&lt;/p&gt;

&lt;p&gt;As with key creation, the relying party sends the client options for the &lt;code&gt;navigator.credentials.get&lt;/code&gt; call. At a minimum these must contain a random nonce/challenge, the rpID and the list of allowed credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s"&gt;'challenge'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_challenge&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'ascii'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s"&gt;'rpId'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;'userVerification'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;'extensions'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;'allowCredentials'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"public-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"AK-r2A2ophbN_fw_xsHxP3z-l-5SVcbZi5Ez9oLgWrY"&lt;/span&gt;
        &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="s"&gt;'timeout'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client passes the option to &lt;code&gt;navigator.credentials.get&lt;/code&gt; and the user is asked to authenticate. If successful, the return value is a &lt;code&gt;PublicKeyCredential&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;The object is sent to the server and looks as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AK-r2A2ophbN_fw_xsHxP3z-l-5SVcbZi5Ez9oLgWrY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"raw_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AK-r2A2ophbN_fw_xsHxP3z-l-5SVcbZi5Ez9oLgWrY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"client_data_json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiWGE2RGdfRTB5TFVZelBja05URDBER2pEcHZtX2JKRDhmazZnbHZJUDc3YyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"authenticator_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MEUCID2sx44Y2iuVdeBZV8BXxeuRGPzsOrbmS0mTCQ6j25SzAiEAwYem5DpkU_oHjVJopmWCSYDUhJpxGtwb7fjZPgZmaKA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user_handle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1EUqUv7528w"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authenticator_attachment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public-key"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The four key steps for the verification procedure are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The challenge in &lt;code&gt;clientData&lt;/code&gt; corresponds to the one the relying party sent&lt;/li&gt;
&lt;li&gt;Verify that the credential id match one of the credentials stored for the user&lt;/li&gt;
&lt;li&gt;The payload contains the sha256 of the relying party id (rpId). The relying party should verify that the hash matches the expected rpId&lt;/li&gt;
&lt;li&gt;Verify that the signature on the blob is a valid signature of the concatenation of the &lt;code&gt;authData&lt;/code&gt; and the sha256 of the &lt;code&gt;clientDataJSON&lt;/code&gt; fields. This signature must be made with the keypair the user is authenticating with&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full verification procedure is rather long and specified &lt;a href="https://www.w3.org/TR/webauthn/#verification-procedure"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Random nonces
&lt;/h3&gt;

&lt;p&gt;For both authentication and credential creation, it is absolutely critical to make sure that nonces/challenges are truly random. Failure to do so would result in vulnerability to replay attacks.&lt;/p&gt;

&lt;p&gt;It is also key for the relying party not to trust the client and its data. The relying party should store the nonce and verify it matches the one returned by the client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security flags
&lt;/h3&gt;

&lt;p&gt;We’ve seen earlier how, as part of the key creation process, &lt;code&gt;navigator.credentials.create&lt;/code&gt; returns a set of flags.&lt;/p&gt;

&lt;p&gt;The flags field is 1 byte in length and contains the following flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bit 0: &lt;a href="https://www.w3.org/TR/webauthn/#concept-user-present"&gt;User Present (UP)&lt;/a&gt; result.

&lt;ul&gt;
&lt;li&gt;1 means the user is present.&lt;/li&gt;
&lt;li&gt;0 means the user is not present.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Bit 1: Reserved for future use (RFU1).&lt;/li&gt;
&lt;li&gt;Bit 2: &lt;a href="https://www.w3.org/TR/webauthn/#concept-user-verified"&gt;User Verified (UV)&lt;/a&gt; result.

&lt;ul&gt;
&lt;li&gt;1 means the user is verified.&lt;/li&gt;
&lt;li&gt;0 means the user is not verified.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Bits 3-5: Reserved for future use (RFU2).&lt;/li&gt;
&lt;li&gt;Bit 6: &lt;a href="https://www.w3.org/TR/webauthn/#attested-credential-data"&gt;Attested credential data&lt;/a&gt; included (AT).
Indicates whether the authenticator added attested credential data.&lt;/li&gt;
&lt;li&gt;Bit 7: Extension data included (ED).
Indicates if the authenticator data has extensions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In particular, Bit 0 indicates that the authenticator asked the user to perform a “user presence test”. The WebAuthn specification says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A &lt;a href="https://www.w3.org/TR/webauthn/#test-of-user-presence"&gt;test of user presence&lt;/a&gt; is a simple form of &lt;a href="https://www.w3.org/TR/webauthn/#authorization-gesture"&gt;authorization gesture&lt;/a&gt; and technical process where a user interacts with an &lt;a href="https://www.w3.org/TR/webauthn/#authenticator"&gt;authenticator&lt;/a&gt; by (typically) simply touching it (other modalities may also exist), yielding a Boolean result.&lt;br&gt;
&lt;br&gt; Note that this does not constitute &lt;a href="https://www.w3.org/TR/webauthn/#user-verification"&gt;user verification&lt;/a&gt; because a &lt;a href="https://www.w3.org/TR/webauthn/#test-of-user-presence"&gt;user presence test&lt;/a&gt;, by definition, is not capable of &lt;a href="https://www.w3.org/TR/webauthn/#biometric-recognition"&gt;biometric recognition&lt;/a&gt;, nor does it involve the presentation of a shared secret such as a password or PIN.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Bit 2 means that the authenticator asked the user to perform a “user verification test”. The specification says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;User Verification&lt;br&gt;
The technical process by which an &lt;a href="https://www.w3.org/TR/webauthn/#authenticator"&gt;authenticator&lt;/a&gt; locally authorizes the invocation of the &lt;a href="https://www.w3.org/TR/webauthn/#authenticatormakecredential"&gt;authenticatorMakeCredential&lt;/a&gt; and &lt;a href="https://www.w3.org/TR/webauthn/#authenticatorgetassertion"&gt;authenticatorGetAssertion&lt;/a&gt; operations. &lt;a href="https://www.w3.org/TR/webauthn/#user-verification"&gt;User verification&lt;/a&gt; MAY be instigated through various &lt;a href="https://www.w3.org/TR/webauthn/#authorization-gesture"&gt;authorization gesture&lt;/a&gt; modalities; for example, through a touch plus pin code, password entry, or &lt;a href="https://www.w3.org/TR/webauthn/#biometric-recognition"&gt;biometric recognition&lt;/a&gt; (e.g., presenting a fingerprint) &lt;a href="https://www.w3.org/TR/webauthn/#biblio-isobiometricvocabulary"&gt;ISOBiometricVocabulary&lt;/a&gt;.&lt;br&gt;
The intent is to distinguish individual users.&lt;br&gt;
&lt;br&gt;Note that user verification does not give the Relying Party a concrete identification of the user, but when 2 or more ceremonies with user verification have been done with that credential it expresses that it was the same user that performed all of them. The same user might not always be the same natural person, however, if multiple natural persons share access to the same authenticator.&lt;br&gt;&lt;br&gt;
These flags coupled with attestation data are key to establish the trustworthiness of a key. In fact a strong authenticator which has created a keypair with both user presence and user verification is a good candidate replacement for MFA, captchas and other forms of verification.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unfortunately, as we have mentioned throughout the article Apple has disabled attestation data on their passkeys significantly reducing the value of these flags.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discoverable credentials vs server-side credentials
&lt;/h3&gt;

&lt;p&gt;You might have noticed in the code snippets above that our toy relying party sent credential IDs to the browser when the authentication ceremony started. Server-side credentials are not discoverable, in fact the specification specifies:&lt;/p&gt;

&lt;p&gt;“This means that the Relying Party must manage the credential’s storage and discovery, as well as be able to first identify the user in order to discover the credential IDs to supply in the navigator.credentials.get() call”&lt;/p&gt;

&lt;p&gt;Hence to work with server-side credentials, the relying party needs to send an array of allowed credentials IDs for the user to login with.&lt;/p&gt;

&lt;p&gt;However discoverable credentials don’t require credential IDs to be sent by the relying party, simply sending the challenge and the rpId is sufficient.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s"&gt;'challenge'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_challenge&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'ascii'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s"&gt;'rpId'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;site_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'relying_party_name'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s"&gt;'userVerification'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;'extensions'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;'timeout'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding new credentials to an account
&lt;/h3&gt;

&lt;p&gt;As discussed, adding new credentials to a user account is a non-standardized flow, which is ripe for abuse. Relying parties need to be careful as attackers are likely to target this flow to compromise user accounts.&lt;/p&gt;

&lt;p&gt;Passkeys make client-side synchronization and backup of credentials easier, hence there would be fewer circumstances in which a user might need to add another passkey to their account or reset their passkey. However, relying parties should account for that scenario nonetheless and the WebAuthn standard specifically encourages the creation of multiple credentials per account.&lt;/p&gt;

&lt;p&gt;There are generally three approaches to this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Only allow a single passkey per account (in this sense, a passkey is a 1:1 replacement for a password). This turns the problem of a lost passkey into an account reset issue.&lt;/li&gt;
&lt;li&gt;Allow multiple passkeys to an account after a Step-Up Authentication step of the same strength. In other words, the user needs to authenticate via another passkey before being able to add another credential.&lt;/li&gt;
&lt;li&gt;Allow multiple passkeys to an account after a Step-Up Authentication step of any strength. The upside here is the user experience, but the downside is that you reduce the security of the account to the security of the authentication factors used to add a new passkey.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ultimately which path you pick is dependent on the sensitivity of your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Passkeys are a very exciting innovation in the authentication space. We are strong believers that identity is the last major area of security that hasn't gone through a significant upgrade in the last 10 years and, as such, is one of the key weaknesses and sources of threats.&lt;/p&gt;

&lt;p&gt;The weakened guarantees of passkeys, compared to standard WebAuthn credentials (in particular, the inability to perform attestation), reduces their usefulness when it comes to having stronger guarantees on the user verification process as well as the security of the private key. Ultimately we are still a long way from getting rid of account takeovers and passwords. However, passkeys are also the first step towards mass adoption of what we believe to be a safer identity posture for users, so we look forward to more websites adopting them.&lt;/p&gt;

&lt;p&gt;We prepared a small playground to see passkeys in action through the SlashID SDK, check it out! &lt;a href="https://github.com/slashid/website-passkeys-playground"&gt;GitHub&lt;/a&gt; and &lt;a href="https://passkeys-playground.slashid.dev/"&gt;live&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>passkeys</category>
      <category>authentication</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Multi-tenancy authentication done right</title>
      <dc:creator>vincenzoiozzo</dc:creator>
      <pubDate>Mon, 17 Apr 2023 14:28:56 +0000</pubDate>
      <link>https://forem.com/vincenzoiozzo/multi-tenancy-authentication-done-right-59k2</link>
      <guid>https://forem.com/vincenzoiozzo/multi-tenancy-authentication-done-right-59k2</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Implementing complex identity structures securely is an extremely challenging task and more often than not becomes a textbook example of sunk cost fallacy.&lt;/p&gt;

&lt;p&gt;Let’s take a B2B SaaS application as an example. Imagine you are developing an issue tracker application like Jira, let's call it Trackalot. The development journey normally goes something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You start with a simple tag, let’s call it &lt;code&gt;organization&lt;/code&gt;, in your users table to group users belonging to different customers, and you embed logic in your app to look up the &lt;code&gt;organization&lt;/code&gt; field for conditional rendering and similar. &lt;br&gt;&lt;br&gt;
This is looking great – a small database migration and a couple of PRs later and you are done (or so you think)!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your company is doing great and you start onboarding your first enterprise customer who has a few hundred users. Your product manager comes in and explains that the deal is not going to close unless the customer can specify their own multi-factor authentication (MFA) policy. So now you rush to create an &lt;code&gt;organizations&lt;/code&gt; table, refactor all your code to account for the new table, migrate the database again and then write some custom logic to handle the different MFA flows that each customer may have.&lt;br&gt;&lt;br&gt;
It’s a fair amount of work but you got this, you crunch a little and deliver!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At this point, you might or might have not gotten the customer from (2) depending on how many sleepless nights you spent writing code. But great news: you now have an organization table and you can extend it easily! Now your product manager comes to you and explains that one of the customers needs custom templates for the magic links sign-in emails. Now you are at a crossroad, do you:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Create a tenant per customer in your Identity and Account Management (IAM) product/solution. A crippling doubt settles in: How are you going to login users belonging to different tenants? You park the thought for now, thinking “hmm, maybe subdomains”.&lt;/li&gt;
&lt;li&gt;Create a field in the &lt;code&gt;organizations&lt;/code&gt; table which links to the custom email template. The problem is that your IAM product doesn’t really support multiple templates per user, so how are you going to send these emails? Firing up your own service to use AWS Simple Email Service (SES)?&lt;/li&gt;
&lt;li&gt;Scrap the IAM product you are using and build all of your identity logic yourself
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eventually you settle on one of these, most likely the first one, and call it victory.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Now your product manager (who’s slowly becoming your work nemesis) comes back to you and says that, actually, what your app really needs is role-based access control (RBAC). So you find yourself wondering:

&lt;ul&gt;
&lt;li&gt;Where do you store these roles?&lt;/li&gt;
&lt;li&gt;How do you enforce them?&lt;/li&gt;
&lt;li&gt;Are they per organization or global?&lt;/li&gt;
&lt;li&gt;Should they be configurable or are they fixed?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Your company launches a new feature where users can belong to multiple organizations or workspaces, just like Figma or GitHub. An impending sense of doom settles in:

&lt;ul&gt;
&lt;li&gt;How are you going to share these users?&lt;/li&gt;
&lt;li&gt;Do you have to mask the user ID so that different organizations can’t correlate users?&lt;/li&gt;
&lt;li&gt;What about different RBAC roles per org?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;This doesn’t even account for: the various vulnerabilities you accidentally introduced while trying to ship as quickly as possible (trust us, they are pretty tough to &lt;a href="https://salt.security/blog/traveling-with-oauth-account-takeover-on-booking-com"&gt;spot&lt;/a&gt;!); the not insignificant user entity reconciliation burden you have now placed on the data science team because you have multiple user tables; and the inability to comply with GDPR and data localization laws that large customers really care about and that will cost you a 7-figure contract.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_mBXXec2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.slashid.dev/blog/ditch-orgs/im_just_trying_to_change_this_lightbulb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_mBXXec2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.slashid.dev/blog/ditch-orgs/im_just_trying_to_change_this_lightbulb.gif" alt="Just changing lightbulbs" width="240" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this is an overly dramatic narrative, it matches pretty consistently what most companies go through: implementing effective and scalable IAM takes several engineers, several lost deals, false starts and months of development effort. Not to mention long term maintenance as your product features (and your backend architecture) grow exponentially.&lt;/p&gt;

&lt;p&gt;Our hope with suborgs is to remove all this undifferentiated complexity and security risk so you can focus on actually implementing the core logic of your app (i.e., the thing you were hired to develop).&lt;/p&gt;

&lt;p&gt;Let’s see how this would look if step 0 had been registering with SlashID.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution with SlashID
&lt;/h2&gt;

&lt;p&gt;Now let’s see how this would have gone if you’d started off using SlashID and our suborganizations features.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You need to separate the users of your different customers → This is available out of the box with SlashID suborgs.
As a SlashID customer, your company has a SlashID organization, which can have as many child suborgs as you need. Each suborg is independent and fully-featured with authentication and user management, but you still retain control. With just one API call you can spin up a new suborg for each new customer you onboard, each with their own user base.&lt;/li&gt;
&lt;li&gt;Your customers have different MFA requirements -&amp;gt; Done!
Each suborg has the full suite of SlashID authentication features, including customisable and step-up MFA. You are free to change your individual suborgs’ configuration to suit your customers’ specific needs.
By using SlashID you didn’t even need to ship a new feature to close a new deal - it was already there, ready to go.&lt;/li&gt;
&lt;li&gt;Your customers want customized templates for authentication emails -&amp;gt; Already done!
As above, each suborg has independent configuration, including customizable email and SMS templates, so each one of your customers can have their own branded comms for their end users. Victory is assured.&lt;/li&gt;
&lt;li&gt;Role-based access control no longer fills you with fear. Each suborg can have its own set of &lt;a href="https://developer.slashid.dev/docs/guides/suborgs"&gt;person groups&lt;/a&gt;. SlashID user JWTs have a claim with a user’s groups, and our React SDK has components for conditional rendering and access by group. Or you can use the token claims on your backend to make authorization decisions.&lt;/li&gt;
&lt;li&gt;Users shared across multiple organizations? No sense of doom here.
By default, each suborg has its own independent user base, with unique IDs per user. However, if you want to add seamless but secure switching between apps, you can do that too: suborgs can be configured to share some information so you get consistent user identity across them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There you have it - minutes of development work instead of months; well-rested engineers; and you get to stay friends with your product manager.&lt;/p&gt;

&lt;p&gt;Jokes aside, we created suborgs after countless conversations with our customers, to respond to the challenges dev teams face when trying to model complex organization structures with their IAM product.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real world example in 10 minutes or less
&lt;/h2&gt;

&lt;p&gt;Going back to the Trackalot example, how would you implement it with suborgs?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can also find more examples in our &lt;a href="https://developer.slashid.dev/docs/guides/suborgs"&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 1: Create a suborg for each customer
&lt;/h3&gt;

&lt;p&gt;Creating a suborg is a simple API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://api.slashid.com/organizations/suborganizations'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-OrgID: &amp;lt;ORGANIZATION_ID&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-API-Key: &amp;lt;API_KEY&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "sub_org_name": "Parks &amp;amp; Rec Dept",
    "groups_org_id": "85637a0a-a574-326a-bac3-d1f46d62dbd9"
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example we are specifying that the suborg should have the same group pool (&lt;code&gt;groups_org_id&lt;/code&gt;) as the parent org - this way&lt;br&gt;
they can share the same RBAC roles.&lt;/p&gt;

&lt;p&gt;This is the response, containing the ID and API key of the new suborg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="s2"&gt;"result"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
       &lt;span class="s2"&gt;"api_key"&lt;/span&gt;: &lt;span class="s2"&gt;"wY7gDtUDjxGMdynd6BKaaLojHFE="&lt;/span&gt;, // API key of the new suborganization
       &lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"97724371-a0a1-4b93-bf51-6ba2feb1acdf"&lt;/span&gt;, // ID of the new suborganization
       &lt;span class="s2"&gt;"org_name"&lt;/span&gt;: &lt;span class="s2"&gt;"Parks &amp;amp; Rec Dept"&lt;/span&gt;, // as &lt;span class="k"&gt;in &lt;/span&gt;the request
       &lt;span class="s2"&gt;"tenant_name"&lt;/span&gt;: “Trackalot&lt;span class="s2"&gt;" // same as for the parent organization
   }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s create another one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://api.slashid.com/organizations/suborganizations'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-OrgID: &amp;lt;ORGANIZATION_ID&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-API-Key: &amp;lt;API_KEY&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "sub_org_name": "Dunder Mifflin Paper Company",
    "groups_org_id": "85637a0a-a574-326a-bac3-d1f46d62dbd9"

}'&lt;/span&gt;


&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"result"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"api_key"&lt;/span&gt;: &lt;span class="s2"&gt;"FkImXyWgZhuqTGTh70fXzuI1PMo="&lt;/span&gt;,
        &lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"d89e29fc-2693-d3b7-764c-debc9561eea2"&lt;/span&gt;,
        &lt;span class="s2"&gt;"Org_name"&lt;/span&gt;: &lt;span class="s2"&gt;"Dunder Mifflin Paper Company"&lt;/span&gt;,
        &lt;span class="s2"&gt;"tenant_name"&lt;/span&gt;: &lt;span class="s2"&gt;"Trackalot"&lt;/span&gt; // same as &lt;span class="k"&gt;for &lt;/span&gt;the parent organization
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have built the following org structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nOgpktYH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/ditch-orgs/orgs_blue.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nOgpktYH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.slashid.dev/blog/ditch-orgs/orgs_blue.jpeg" alt="Suborgs" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that the Parks &amp;amp; Rec Dept and Dunder Mifflin Paper Company are ready, we are one step closer to helping them avoid those 94 meetings a day with Trackalot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Customize authentication policies
&lt;/h3&gt;

&lt;p&gt;You can use the &lt;a href="https://developer.slashid.dev/docs/category/api/core/organizations"&gt;Organization APIs&lt;/a&gt; to customize the behavior of suborgs. So for example, you can change the email template used for magic links by the Parks &amp;amp; Rec Dept suborg with the following call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://api.slashid.com/organizations/suborganizations'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-OrgID: 97724371-a0a1-4b93-bf51-6ba2feb1acdf'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-API-Key: wY7gDtUDjxGMdynd6BKaaLojHFE='&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "email_authn_challenge": &amp;lt;EMAIL_TEMPLATE&amp;gt;
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This won’t impact the template for Dunder Mifflin Paper Company or any other suborg.&lt;/p&gt;

&lt;p&gt;Note how problems (2) and (3) from the scenario above are solved with a single API call vs endless coding, scoping and back and forth with your product manager.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create roles for RBAC and assign them to users
&lt;/h3&gt;

&lt;p&gt;Now you have some customers, you can create groups to more easily manage users, starting with &lt;code&gt;employee&lt;/code&gt; and &lt;code&gt;admin&lt;/code&gt;, using the &lt;a href="https://developer.slashid.dev/docs/api/access/create-a-group/#create-a-group"&gt;SlashID groups API&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://api.slashid.com/groups'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-OrgID: 85637a0a-a574-326a-bac3-d1f46d62dbd9'&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;// &lt;span class="s2"&gt;"Trackalot"&lt;/span&gt; org ID
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-API-Key: /cgMbOUYjFGMUv4hIvKoMxhVIJ0='&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;// &lt;span class="s2"&gt;"Trackalot"&lt;/span&gt; API key
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name": "employee"
}'&lt;/span&gt;

curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://api.slashid.com/groups'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-OrgID: 85637a0a-a574-326a-bac3-d1f46d62dbd9'&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;// &lt;span class="s2"&gt;"Trackalot"&lt;/span&gt; org ID
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-API-Key: /cgMbOUYjFGMUv4hIvKoMxhVIJ0='&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;// &lt;span class="s2"&gt;"Trackalot"&lt;/span&gt; API key
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name": "admin"
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We used the organization ID for Trackalot to create the groups, and we see that listing the groups for the suborgs returns the expected result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://api.slashid.com/groups'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-OrgID: d89e29fc-2693-d3b7-764c-debc9561eea2'&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;// Dunder Mifflin Paper Company org ID
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-API-Key: FkImXyWgZhuqTGTh70fXzuI1PMo='&lt;/span&gt; // Dunder Mifflin Paper Company API key

&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"result"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"employee"&lt;/span&gt;,
        &lt;span class="s2"&gt;"admin"&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same API call with the Parks &amp;amp; Rec organization ID returns the same results, as expected - they share a group pool, so the groups are shared.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://api.slashid.com/groups'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-OrgID: 97724371-a0a1-4b93-bf51-6ba2feb1acdf'&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;// Parks &amp;amp; Rec Dept org ID
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-API-Key: wY7gDtUDjxGMdynd6BKaaLojHFE='&lt;/span&gt; // Parks &amp;amp; Rec Dept API key

&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"result"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"employee"&lt;/span&gt;,
        &lt;span class="s2"&gt;"admin"&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is outside the scope of this blogpost but with SlashID suborg you have the freedom to not share users or groups pools. See our documentation for a few examples of how to do that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Sharing users
&lt;/h3&gt;

&lt;p&gt;Lastly, if Trackalot takes off and you want to allow end users to be part of multiple organizations, you can also do that easily through our person pools.&lt;/p&gt;

&lt;p&gt;By default, suborgs have their own dedicated pool of users, but they can also share users through person pools. This means that if the same person works for both Parks &amp;amp; Recs and Dunder Mifflin, they will have a consistent unique identifier in all suborgs sharing that person pool as long as they use the same handles (email addresses or phone numbers) to register.&lt;/p&gt;

&lt;p&gt;You can read more about person pools in our &lt;a href="https://developer.slashid.dev/docs/guides/suborgs"&gt;guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But it doesn't end there, thanks to our buckets you are also able to share &lt;a href="https://developer.slashid.dev/docs/concepts/attribute_buckets"&gt;attributes&lt;/a&gt; across users and organizations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this blogpost we’ve shown how you can use SlashID suborgs to implement complex user structures flexibly and securely, without wasting months of dev time on it and without risking a breach.&lt;/p&gt;

&lt;p&gt;Look out for future guides and blog posts, where we will deep dive into more case studies to explore DataVault, attribute-based access control (ABAC), and role-based access control (RBAC).&lt;/p&gt;

&lt;p&gt;Have questions or want to find out more? Check out our &lt;a href="https://developer.slashid.dev/"&gt;documentation&lt;/a&gt; or &lt;a href="//mailto:support@slashid.dev"&gt;reach out to us&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ready to get started with SlashID? &lt;a href="https://www.slashid.dev/request-access/"&gt;Register here!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Read more
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.slashid.dev/docs/guides/suborgs"&gt;More suborg use cases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.slashid.dev/blog/groups-react/"&gt;Conditional RBAC with our React SDK&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>authentication</category>
      <category>authorization</category>
      <category>security</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Using Google Tink to sign JWTs with ECDSA</title>
      <dc:creator>vincenzoiozzo</dc:creator>
      <pubDate>Wed, 22 Feb 2023 11:19:44 +0000</pubDate>
      <link>https://forem.com/vincenzoiozzo/using-google-tink-to-sign-jwts-with-ecdsa-3g6n</link>
      <guid>https://forem.com/vincenzoiozzo/using-google-tink-to-sign-jwts-with-ecdsa-3g6n</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this blog post, we will show how the Tink cryptography library can be used to create, sign, and verify JSON Web Tokens (JWTs), as well as to manage the cryptographic keys for doing so. This is intended as a more practical example to complement Tink’s own documentation on the underlying cryptographic theory and their approach.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://www.slashid.dev" rel="noopener noreferrer"&gt;SlashID&lt;/a&gt; we are big fan of Tink and use it in a variety of roles, including signing tokens! Read more about it on our &lt;a href="https://www.slashid.dev/blog" rel="noopener noreferrer"&gt;blog&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;We chose Tink because of three core characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Tink philosophy of an easy to use API that is difficult to mess up and with safe defaults - we’ll see below how picking the wrong library could make your token service entirely insecure&lt;/li&gt;
&lt;li&gt;Key rotation and management out of the box&lt;/li&gt;
&lt;li&gt;Tink encryption APIs and integration with KMS makes it easy to protect the signing key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we dive into the tutorial, let’s do a brief recap of JWTs.&lt;/p&gt;

&lt;h3&gt;
  
  
  JSON Web Tokens
&lt;/h3&gt;

&lt;p&gt;JWTs are an industry standard and are widely used in the identity, authentication, and user management space. They are used to transfer information between two parties in a verifiable manner. There are many excellent introductions to JWTs, so for the purposes of this discussion we will focus on the structure.&lt;/p&gt;

&lt;p&gt;JWTs are typically transmitted as base-64 encoded strings, and are composed of three parts separated by periods:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A header containing metadata about the token itself&lt;/li&gt;
&lt;li&gt;The payload, a JSON-formatted set of claims&lt;/li&gt;
&lt;li&gt;A signature that can be used to verify the contents of the payload&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, this JWT&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;eyJhbGciOiJIUzI&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;NiIsInR&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;cCI&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;IkpXVCJ&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="err"&gt;.eyJzdWIiOiIxMjM&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;NTY&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;ODkwIiwibmFtZSI&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;IkpvZSBTbGFzaElEIiwiaWF&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;IjoxNTE&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;MjM&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;MDIyfQ.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&gt;cL&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="err"&gt;NsNCXLPEvmvNGxHN&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;wLuarpp&lt;/span&gt;&lt;span class="mi"&gt;98&lt;/span&gt;&lt;span class="err"&gt;wwezHnSt&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;fqg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;has the following parts&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Encoded value&lt;/th&gt;
&lt;th&gt;Decoded value&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Header&lt;/td&gt;
&lt;td&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9&lt;/td&gt;
&lt;td&gt;{ "alg": "HS256", "typ": "JWT"}&lt;/td&gt;
&lt;td&gt;Indicates that this is a JWT and that it was hashed with the HS256 algorithm (HMAC using SHA-256)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payload&lt;/td&gt;
&lt;td&gt;eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvZSBTbGFzaElEIiwiaWF0IjoxNTE2MjM5MDIyfQ&lt;/td&gt;
&lt;td&gt;{"sub": "1234567890", "name": "SlashID User", "iat": 1516239022}&lt;/td&gt;
&lt;td&gt;Payload with claims about a user and the token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signature&lt;/td&gt;
&lt;td&gt;4cL42NsNCXLPEvmvNGxHN3wLuarpp98wwezHnSt2fqg&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;The signature generated using the HS256 algorithm that verifies the payload&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important aspect of this is that the JWT is &lt;strong&gt;signed, meaning that the claims in the payload can be verified&lt;/strong&gt;, if one has access to the appropriate cryptographic key.&lt;/p&gt;

&lt;p&gt;The signature of a JWT token is calculated as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;signAndhash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;base64UrlEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;base64UrlEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;signAndhash&lt;/code&gt; is the signing and hashing algorithms specified in the &lt;code&gt;alg&lt;/code&gt; header. The &lt;a href="https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms" rel="noopener noreferrer"&gt;JOSE IANA&lt;/a&gt; page contains the list of supported algorithms.&lt;/p&gt;

&lt;p&gt;In the example above HS256 stands for HMAC using SHA-256 and the &lt;code&gt;secret&lt;/code&gt; is a 256-bit key.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Signing is &lt;em&gt;not&lt;/em&gt; the same as encryption - even without the cryptographic key for verifying, anybody can decode the token payload and inspect the contents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Task: Creating and Verifying Signed JWTs
&lt;/h2&gt;

&lt;p&gt;Suppose we have been tasked with building a system to securely sign and verify JWTs for an authenticated user. We will focus on building a small HTTP server that exposes two endpoints: one for generating a signed JWT based on some user information, and one for verifying an existing JWT. We will use asymmetric keys for this - meaning the key has a private part (for signing) and a public part (for verifying). Examples of asymmetric key algorithms include RSA and ECDSA.&lt;/p&gt;

&lt;h3&gt;
  
  
  Picking the right algorithm
&lt;/h3&gt;

&lt;p&gt;Before we begin, it’s crucial to understand which signing algorithm to use and what are the pros and cons. As discussed earlier the algorithm used to sign the JWT is specified in the header in the &lt;code&gt;alg&lt;/code&gt; field, and there are several options for signing and hashing algorithms.&lt;/p&gt;

&lt;p&gt;Let’s start with the hashing algorithm. The most common algorithm for hashing is SHA, and an intuitive way to think about hashing security is that the level of security each one gives you is 50% of their output size. So SHA-256 will provide you with 128-bits of security, and SHA-512 will provide you with 256-bits of security. This means that an attacker will have to generate 2^128 hashes before they start finding collisions. Generally anything above 256-bits is considered acceptable.&lt;/p&gt;

&lt;p&gt;When it comes to signing, historically, RS256 (RSASSA-PKCS1-v1_5) RS256 has been the default for most JWT implementations. JWTs signed with RSASSA-PKCS1-v1_5 have a deterministic signature, meaning that the same JWT header &amp;amp; payload will always generate the same signature.&lt;/p&gt;

&lt;p&gt;Even though there are no known attacks against RSASSA-PKCS1-v1_5 its usage is discouraged in favor of Elliptic Curves/ECDSA. And in certain cases, like for the &lt;a href="https://www.openbanking.org.uk/" rel="noopener noreferrer"&gt;UK Open Banking&lt;/a&gt; standard, RSASSA-PKCS1-v1_5 is forbidden.&lt;/p&gt;

&lt;p&gt;When it comes to ECDSA, the most common choice is &lt;code&gt;ES256&lt;/code&gt; which stands for ECDSA using P-256 (an elliptic curve also known as secp256r1) and SHA-256. ECDSA requires much shorter keys than RSA in terms of strength (you need 3072 bits for an RSA key to have the same strength of P-256) and key generation is faster than RSA even though verification is generally slower.&lt;/p&gt;

&lt;p&gt;ECDSA, by default, uses a random nonce that is generated per signature, hence ECDSA-generated signatures are non-deterministic.&lt;/p&gt;

&lt;p&gt;The random nonce is key, as reusing the random nonce or having easily-guessable bits in the nonce can make the private key easily recoverable. Two high profile cases of such incidents were Sony's Playstation 3 and Bitcoin. In the Playstation 3 case, the private key was recovered due to a static nonce, and in Bitcoin’s case, Android users were affected due to a bug in Java’s &lt;code&gt;SecureRandom&lt;/code&gt; class on Android.&lt;/p&gt;

&lt;p&gt;Given the risk, it is key to pick a library that safely implements ECDSA either by using a deterministic scheme as described by &lt;a href="https://datatracker.ietf.org/doc/html/rfc6979" rel="noopener noreferrer"&gt;RFC 6979&lt;/a&gt; or by implementing the algorithm in a way that doesn’t depend on the quality of the random value - for example, see &lt;a href="https://github.com/square/go-jose/issues/357" rel="noopener noreferrer"&gt;this thread&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For this blogpost we’ll use ES256. If you are implementing your own signing service, please refer to &lt;a href="https://datatracker.ietf.org/doc/rfc8725/" rel="noopener noreferrer"&gt;RFC 8725&lt;/a&gt; for current best practices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Creating, Signing, and Verifying a JWT
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;For this blogpost we will use Tink’s &lt;a href="https://pkg.go.dev/github.com/google/tink/go@v1.7.0" rel="noopener noreferrer"&gt;Go library&lt;/a&gt;, but there are published libraries for several other languages, and the principles are the same.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To begin with, let’s take a look at the &lt;a href="https://pkg.go.dev/github.com/google/tink/go@v1.7.0/jwt" rel="noopener noreferrer"&gt;Tink documentation on JWTs&lt;/a&gt;. There are five types that we are interested in to begin with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;jwt.RawJWT&lt;/code&gt; - an unverified JWT&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jwt.VerifiedJWT&lt;/code&gt; - a JWT that has been verified&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jwt.Signer&lt;/code&gt; - an interface for signing JWTs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jwt.Verifier&lt;/code&gt; - an interface for verifying signed and encoded JWTs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;keyset.Handle&lt;/code&gt; - the type representing a cryptographic keyset, used to create instances implementing &lt;code&gt;Signer&lt;/code&gt; and &lt;code&gt;Verifier&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lifecycle of a JWT is&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.slashid.dev%2Fblog%2Ftink-jwt%2Fjwt_lifecycle.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.slashid.dev%2Fblog%2Ftink-jwt%2Fjwt_lifecycle.jpg" alt="SlashID Documentation Site" width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we want to build our &lt;code&gt;RawJWT&lt;/code&gt; that will be signed. This includes both the registered claims as described in the &lt;a href="https://www.rfc-editor.org/rfc/rfc7519" rel="noopener noreferrer"&gt;JWT specification&lt;/a&gt;, and some custom claims:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;   &lt;span class="n"&gt;jwtID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

   &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="n"&gt;expiry&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenDuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawJWTOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;Issuer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tokenIssuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;JWTID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;jwtID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;IssuedAt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ExpiresAt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;NotBefore&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;CustomClaims&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="n"&gt;rawJWT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRawJWT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to create new RawJWT: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have our &lt;code&gt;RawJWT&lt;/code&gt;, we can sign and encode it. First, we need a Signer implementation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that in the example &lt;code&gt;jwt&lt;/code&gt; refers to the &lt;a href="https://github.com/google/tink/tree/master/go/jwt" rel="noopener noreferrer"&gt;Tink jwt package&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;   &lt;span class="n"&gt;signingKeyset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keysetsRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetKeyset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to get token signing keyset: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewSigner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signingKeyset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to create new Signer: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To do this, we have introduced an interface, &lt;code&gt;KeysetsRepo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;KeysetsRepo&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;GetKeyset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is the interface to whatever we are using to store the keysets. We will come back to the details of this later - for now, we can simply define the interface, which is a single method for getting a keyset. Once we have that keyset, we can create a new JWT signer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not all keysets can be used to create a &lt;code&gt;Signer&lt;/code&gt; - the keyset must have been created using one of the templates defined in the JWT library, as discussed below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, we can sign the token and return the signed token string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;   &lt;span class="n"&gt;signedToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SignAndEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawJWT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to sign RawJWT: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;signedToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now we have the whole method for signing JWTs with Tink.&lt;/p&gt;

&lt;p&gt;Now let’s implement the second part of the lifecycle and verify the token. First, we create a &lt;code&gt;Verifier&lt;/code&gt; instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;   &lt;span class="n"&gt;verificationKeyset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keysetsRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPublicKeyset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to get token verification keyset: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewVerifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verificationKeyset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to create new Verifier: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this, we have added a new method to the &lt;code&gt;KeysetsRepo&lt;/code&gt; interface, &lt;code&gt;GetPublicKeyset&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;KeysetsRepo&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;GetKeyset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;GetPublicKeyset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we are implementing signing with asymmetric keys, we deal with two keysets - the private one for signing tokens, and the public one for verifying. The former must be stored securely and kept secret, otherwise anyone can sign tokens as if they were you, making token verification meaningless. The latter can be published, for example as part of an &lt;a href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata" rel="noopener noreferrer"&gt;OIDC discovery document&lt;/a&gt;. In this case we make the separation clear by having two methods in the repo, one for getting the full keyset, and one for getting just the public part.&lt;/p&gt;

&lt;p&gt;We also need to define a &lt;code&gt;Validator&lt;/code&gt; that the &lt;code&gt;Verifier&lt;/code&gt; can use to check the token claims:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;   &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidatorOpts&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="n"&gt;ExpectedIssuer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tokenIssuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;IgnoreTypeHeader&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;IgnoreAudiences&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ExpectIssuedInThePast&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to create new Validator: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are ignoring some fields in this example to keep it shorter.&lt;/p&gt;

&lt;p&gt;Finally, we can decode and verify the token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;   &lt;span class="n"&gt;verifiedJWT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VerifyAndDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signedToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;InvalidTokenError&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;verifiedJWT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have the whole method for verifying JWTs as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storing the Signing Keysets
&lt;/h3&gt;

&lt;p&gt;The next step is to implement our &lt;code&gt;KeysetsRepo&lt;/code&gt;, which is the interface between our service and however we are persisting our keysets. Safely persisting our token keysets is essential - if we were to lose them, all existing tokens would need to be invalidated, which could have a significant impact on the user experience for anybody using the service to create and verify tokens.&lt;/p&gt;

&lt;p&gt;For this example, we will implement a very simple keysets repo that stores the keys in the local file system. While this would typically not be suitable for large-scale distributed systems, it serves to illustrate the essential points of persisting and retrieving keysets. The keyset will be stored in a single file in the working directory. Before we begin to implement the methods needed for our interface, we will write a method for initializing the keyset, &lt;code&gt;InitTokenKeyset&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;FSKeysetsRepo&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;keysetsFilePath&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
   &lt;span class="n"&gt;masterKey&lt;/span&gt;       &lt;span class="n"&gt;tink&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AEAD&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewFSKeysetsRepo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keysetsFilePath&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;masterKey&lt;/span&gt; &lt;span class="n"&gt;tink&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AEAD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FSKeysetsRepo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;FSKeysetsRepo&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="n"&gt;keysetsFilePath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;keysetsFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;masterKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;masterKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FSKeysetsRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;InitTokenKeyset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ES256Template&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to create new keyset handle with ES256 template: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeKeysetToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FSKeysetsRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;writeKeysetToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keysetsFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to create keysets file %s: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keysetsFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// unhandled error&lt;/span&gt;

   &lt;span class="n"&gt;jsonWriter&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewJSONWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;masterKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to write keyset to file %s: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keysetsFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we create a new keyset using &lt;code&gt;keyset.NewHandle&lt;/code&gt; and the &lt;code&gt;ES256Template&lt;/code&gt; from the &lt;code&gt;jwt&lt;/code&gt; package. This creates a keyset with the ES256 algorithm, which implements elliptic curve signing with the NIST P-256 curve. As mentioned above, this creates a keyset suitable for creating &lt;code&gt;Signer&lt;/code&gt; and &lt;code&gt;Verifier&lt;/code&gt; instances. Tink provides many other templates for keysets that cannot be used for signing/verifying, and trying to use one as such will result in a runtime error. The &lt;code&gt;jwt&lt;/code&gt; subpackage in Tink lists the available templates.&lt;/p&gt;

&lt;p&gt;Once we have the new keyset handle, we can serialize it to JSON and write it to a file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;   &lt;span class="n"&gt;jsonWriter&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewJSONWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;masterKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to write keyset to file %s: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keysetsFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We create a &lt;code&gt;JSONWriter&lt;/code&gt; instance for the file, and then call the keyset handle’s &lt;code&gt;Write&lt;/code&gt; method. Note that this takes two arguments - an instance of the &lt;code&gt;Writer&lt;/code&gt; implementation, and a &lt;code&gt;tink.AEAD&lt;/code&gt; instance, which we have called &lt;code&gt;masterKey&lt;/code&gt;. This is the encryption key used to encrypt the keyset before writing it to the file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tink provides various other implementations of &lt;code&gt;Writer&lt;/code&gt;; we have chosen JSON to be more human-readable, so the keyset files can be inspected.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we can create a keyset and securely store it in a local file. We can now return to implementing the methods for the &lt;code&gt;KeysetsRepo&lt;/code&gt; interface. First, &lt;code&gt;GetKeyset&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FSKeysetsRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetKeyset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readKeysetFromFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FSKeysetsRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;readKeysetFromFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keysetsFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to open keysets file %s: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keysetsFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// unhandled error&lt;/span&gt;

   &lt;span class="n"&gt;jsonReader&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewJSONReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;masterKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to read keyset as JSON: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will need to re-use the &lt;code&gt;readKeysetFromFile&lt;/code&gt; logic later, so we have extracted it to a separate method. Here, we open the file holding the keyset, and create a &lt;code&gt;JSONReader&lt;/code&gt; (since we stored the keyset serialized as JSON). Then we use the &lt;code&gt;keyset.Read&lt;/code&gt; method to read the keyset from the file and create a &lt;code&gt;keyset.Handle&lt;/code&gt;. Note that again the master key is needed, this time to decrypt the keyset.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GetPublicKeyset&lt;/code&gt; method is very similar, with one additional step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FSKeysetsRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetPublicKeyset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;keyset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readKeysetFromFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="n"&gt;publicHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Public&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to get public keyset: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;publicHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We get the public part of the keyset only using the &lt;code&gt;handle.Public()&lt;/code&gt; method, which returns a new keyset containing only the public part, which we then return.&lt;/p&gt;

&lt;h3&gt;
  
  
  The API
&lt;/h3&gt;

&lt;p&gt;The last remaining piece of our service is an API that can be used to create and verify tokens. For this example, we will implement a very basic HTTP server exposing two endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;StartServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;APIHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tokens"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PostToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tokens/verify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PostVerifyToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both endpoints will accept only the &lt;code&gt;POST&lt;/code&gt; method. &lt;code&gt;POST /tokens&lt;/code&gt; will accept some user information and return a signed and encoded JWT; &lt;code&gt;POST /tokens/verify&lt;/code&gt; will accept a signed token and return a boolean indicating whether it is valid.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PostToken&lt;/code&gt; can leverage &lt;code&gt;GenerateTokenWithClaims&lt;/code&gt; to generate a signed token and &lt;code&gt;PostVerifyToken&lt;/code&gt; can leverage &lt;code&gt;VerifyToken&lt;/code&gt; to verify a token.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Master Key
&lt;/h3&gt;

&lt;p&gt;Our service is nearly complete - we have our API, a layer of business logic for managing our tokens, and persistence for our keysets. It’s time to put it all together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getConfigFromEnv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

   &lt;span class="n"&gt;keysetsRepo&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewFSKeysetsRepo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keysetsFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;masterKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;tokenManager&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewTinkTokenManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keysetsRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;apiHandler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewAPIHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;keysetsRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitTokenKeyset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to initialize token keysets"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StartServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Server encountered an error: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will not worry too much about getting the configuration from the environment variables for now - however, we are missing the master key, which we need to construct our keysets repo.&lt;/p&gt;

&lt;p&gt;As discussed above, the master key is used to encrypt your signing keysets, and so it is essential that it is kept securely and safely. If you were to lose the master key, all of your signing keysets would become inaccessible and unusable. It is therefore recommended that you store the master key in a secure key management service (KMS). KMSs are offered as features by cloud providers (e.g., GCP, AWS) and so can be integrated as part of your cloud deployment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For local testing, it is possible to create keysets and store them in plaintext in local files. The example repo contains code for doing this to help you try out the service locally. However, this is not safe for non-testing scenarios!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We will use the GCP KMS as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getMasterKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;tink&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AEAD&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;gcpClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gcpkms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClientWithOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keyURIPrefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterKMSClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gcpClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;masterKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gcpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetAEAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;masterKeyURI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to retrieve master key: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;masterKey&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;masterKey&lt;/code&gt; we return here implements the &lt;code&gt;tink.AEAD&lt;/code&gt; interface for encrypting and decrypting. However, we do not have the master key itself locally - that must be kept safely in the KMS. Instead, the data to be encrypted/decrypted is transmitted to and from the KMS behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this blogpost, we have laid out the essential steps to get started using Tink with JWTs, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating Tink keysets for signing and verifying keys&lt;/li&gt;
&lt;li&gt;Safely storing these keysets&lt;/li&gt;
&lt;li&gt;Using the Signer and Verifier interfaces for JWTs&lt;/li&gt;
&lt;li&gt;Using a KMS to store master keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our next blogpost in the series will build on this, looking at key management (in particular, key rotation), and taking a deeper dive into the structure of a Tink keyset.&lt;/p&gt;

</description>
      <category>showdev</category>
    </item>
    <item>
      <title>SlashID sign-in/sign-up React components</title>
      <dc:creator>vincenzoiozzo</dc:creator>
      <pubDate>Fri, 20 Jan 2023 09:22:04 +0000</pubDate>
      <link>https://forem.com/vincenzoiozzo/slashid-sign-insign-up-react-components-26mn</link>
      <guid>https://forem.com/vincenzoiozzo/slashid-sign-insign-up-react-components-26mn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As mentioned in a &lt;a href="https://www.slashid.dev/blog/react-sdk-v1/" rel="noopener noreferrer"&gt;previous blog post&lt;/a&gt;, we have been working on a set of UI components with the common goal of delivering a streamlined, low friction onboarding experience to our customers. Today we’re happy to announce the next step in that journey and release our sign-up/sign-in form component.&lt;/p&gt;

&lt;p&gt;If you are a SlashID customer you can install our &lt;code&gt;@slashid/react&lt;/code&gt; package to use the components immediately.&lt;/p&gt;

&lt;p&gt;If you are not a customer, you can check out the code in our &lt;a href="https://github.com/slashid/javascript/tree/main/packages/react/src/components" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; and we'd love to give you a test account to try it out - just send as an email at &lt;code&gt;hello@slashid.dev&lt;/code&gt; or &lt;a href="https://www.slashid.dev/request-access" rel="noopener noreferrer"&gt;click here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;The authentication form is a drop-in React component available to all our customers through the official SlashID’s React SDK. It supports all the authentication methods that our core SDK offers out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebAuthn&lt;/li&gt;
&lt;li&gt;Magic link via email or SMS&lt;/li&gt;
&lt;li&gt;One-time passwords&lt;/li&gt;
&lt;li&gt;Single sign-on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In order to use the component, customers only need to specify the authentication methods they want to use in the configuration object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;Configuration is driven by a simple JavaScript object as illustrated below.&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/IcZcF6_WPFU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This example will result with the component rendering an email input for webauthn and two buttons for the login via SSO providers. The component instantly responds to any changes in the configuration and re-renders the UI, giving you a flexible and powerful platform for no-code experimentation.&lt;/p&gt;

&lt;p&gt;The configuration object can be constructed dynamically or can also be fetched from a remote service. This facilitates a convenient way to experiment with the onboarding process and improve the related metrics without deploying any code changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customization
&lt;/h3&gt;

&lt;p&gt;The form is responsive and it will also adapt to dark and light display modes accordingly.&lt;/p&gt;

&lt;p&gt;You can apply your own branding through standard &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties" rel="noopener noreferrer"&gt;CSS variables&lt;/a&gt;. This approach is both performant and flexible as it is not locked into a particular library or a build system.&lt;br&gt;
Using documented CSS variables you can customize the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logo&lt;/li&gt;
&lt;li&gt;Font family&lt;/li&gt;
&lt;li&gt;Color palette&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If further customization is required, all the building blocks of this component expose public CSS class names that can be used to select and style the elements in place using CSS.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/b9BXUIGlSSM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Text content and i18n
&lt;/h3&gt;

&lt;p&gt;All the text content present in the form can be edited. In order to keep the component as light as possible there’s no built in i18n solution – you can simply pass in the translated strings using the configuration interface and the i18n library you already use.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next
&lt;/h2&gt;

&lt;p&gt;SlashID’s Authentication React components are just a piece of the puzzle as we aim to provide a full user onboarding solution.&lt;/p&gt;

&lt;p&gt;More blog posts are coming up on how to improve your onboarding conversion using SlashID so stay tuned!&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>career</category>
      <category>workplace</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Fetching Google Groups with SlashID SSO</title>
      <dc:creator>vincenzoiozzo</dc:creator>
      <pubDate>Wed, 18 Jan 2023 00:56:40 +0000</pubDate>
      <link>https://forem.com/vincenzoiozzo/fetching-google-groups-with-slashid-sso-195l</link>
      <guid>https://forem.com/vincenzoiozzo/fetching-google-groups-with-slashid-sso-195l</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Using IdP groups to make authorization decisions is very common among organizations. For example, Figma wrote a custom &lt;a href="https://www.figma.com/blog/inside-figma-securing-internal-web-apps/" rel="noopener noreferrer"&gt;application gateway&lt;/a&gt; to protect internal web applications using the IdP groups to drive RBAC.&lt;/p&gt;

&lt;p&gt;Whether you are using &lt;a href="https://developer.slashid.dev/docs/gate" rel="noopener noreferrer"&gt;Gate&lt;/a&gt; to make authorization decisions and protect applications, or adopting a different approach, SlashID allows you to retrieve Google Groups for a given user when the user authenticates.&lt;/p&gt;

&lt;p&gt;Google doesn’t provide a way to retrieve a user group when a user authenticates via SSO. To overcome this, we use the Google Admin SDK to retrieve the groups the user belongs to. When the user authenticates through SSO with SlashID, the user token will include a structure similar to the following where the &lt;code&gt;oidc_groups&lt;/code&gt; are contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;google:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xIh3qoOXAFTaiOma2ZS6eCgP7rJF6iNDzleux+t69h8XktpsbHDKpiuwH2SwDO5/pSfwwn1+GeYZjxYxGbcJHAfiTztUfz3XqiejND4NasfuCGs550d0YSruxuC5yeKN9GUBbap1t/El7db23GlzjdJodp85ZFJsYsF9NTvP7ws4LkaX64s4VvlVWVT4fxOctFjO3oE1iR/oOBD1QCawvPdrLomQYFcXzN0R1UY9mIlDtUvsXh9cT+gAT9vXdjZ+Q9hR/b2QVR/l241AzmZ4VeIbVPQ="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"32189023109-4332109.apps.googleusercontent.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1673092551&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lVddu2HJAGO/VEnyh30vEeYTH169x2r+/X2/kImNUNeyt6lpDJaJMF7TwSDwmTdAf4Sxs6Ul5eTCrIfHk73F8Ec7WUa/bjekZoj7Ee/xPYNmZpjGuTNpcQCwl+MfEbdd
zXPPFoCdZF+CcZxldSOwzLUDYcLgXZ8fpBMg2erIMtqaH5e682GwY7iJD4ySUeTj3JftxQYBYizUUPfueNkbgsh+bC1bUtzMHDjVU53kZFvygjYydThtw8gl7Kw5G3NW
6f9Ch/D/9WnFbyb/Q3eibJeIKnMNrow003l70DefZzRLM2/6mkEd/VEMuGo9ejW2An2zf/vVQSKyxXP6ySNSkU/nrTtkGFS0s/ENsCB6taj12i57JOtzgDiigJayXB8N
KKgWtJmT/ESgLxsH54Mg3AYK91wo53jmfo9ZLPn88muczG96GfAzUMAOM5Drl/MKyPP+WZ+zwJ1gnoRqnxIUrv+WDwkwbbvA2vjkW8Op+gXVkactQCQVQ26GBGDPh7MtbTUSsW6LKb8/3oEMBuSIZQ=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"oidc_groups"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"test-group@slashid.dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"sso-guide@slashid.dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"refresh_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuring SlashID to retrieve Google groups is a simple four step process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure the Google credentials&lt;/li&gt;
&lt;li&gt;Register the credentials with SlashID&lt;/li&gt;
&lt;li&gt;Add the credentials to your SlashID organization’s OIDC configuration&lt;/li&gt;
&lt;li&gt;Pass &lt;code&gt;requires_groups&lt;/code&gt; to the authentication calls&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configuring SlashID to retrieve groups
&lt;/h2&gt;

&lt;p&gt;To retrieve Google groups, we need to create a service account with the &lt;code&gt;https://www.googleapis.com/auth/admin.directory.group.readonly&lt;/code&gt; scope. Our developer &lt;a href="https://developer.slashid.dev/docs/guides/SSO/oauth_creds_googlegroups" rel="noopener noreferrer"&gt;guide&lt;/a&gt; has step by step instructions on how to configure your Google organization.&lt;/p&gt;

&lt;p&gt;Once the account is created and you obtain the JSON credentials from GCP, we are ready to configure SlashID to use them. The first step is to add a &lt;code&gt;super_user&lt;/code&gt; field to the end of the JSON object with the email address of a super-admin for the Google account. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service_account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-project-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"private_key_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8167c485286f057e06e4a9d17e99ab4913627dbd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"private_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-----BEGIN PRIVATE KEY-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCiaBGNydTgs0WV&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;umdNqzt5FlqigilYVk4J2oQqimg6Cnf8Q27ChShgDzUfwMbImm65MRFWD8UbiVGs&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Z9Q36bK53HKPgi07sZtdxuuwx2/CZ24l1q1N3Dpv5pfYzm2Z24pJbzWIF1pvDnmp&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;ZLA0iEXCnvOUIZ2tZ1sYmC7KH1MAbip7LNa6pJ/3fA1dRW7NWvporqFZCaGNsGwM&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;0vNtS6dEwayhh8IFh2tCSARXOP62ji3BppTUItqBoh3mqP5L7iTv9iXTKTcTXb9E&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;m5U1Espzc//UbElWashstYksTZYa6yrD7p2Zns2T4xRASL7j33dT5uHzIkvfeRdu&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;yPj09W9TAgMBAAECggEASBj3IgDl1lL/oza7QYmwv1KjLd2mySaXQlyVq+UB3DJl&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;jcHJ2+UNRYe6x7vnA4s7eE9GKPSbRlwxu93kImZHB6fL29WoiwWPuZPjcfk3rhAI&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;oBernBMWhjLSWldZ5KHHxE3wb9geN4svi3m9l7Sfc4TpEWvS+fYWRNbafrRlPpz0&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;QoFDSQy9FqxB7tGhs7insl+N16C8eQyjaysQt/xvk7pUQO6tYpVU1RVKIZlzjesU&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;vOx2NfZSktILhB4teFYRQbGU0trPDfXOfj/NEQ1TUrL0gARHjYjalyrPgk2DNzXH&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;CEx6ImQEKHLiJ9YeOGWvCkMBOb66kstlSwm6PxDf4QKBgQDXbvFKFmco970Yqg79&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;uwJkOA29Wtqz+J9RnQd9WGL2wbFk/NCpqDnmbNM7UQm6nS9fhNkNG2BbBbW1gaYG&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;+5sQ6X69ctxSVvfBqeFwwSdDqz6VReyGeQO7pNT51Ze4cW7O3BIKEoxicAgrw5uR&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;AIUvHKs+UYtipAt0Y/I5GMdbSQKBgQDA/PGsnVdnPHuaPHbdJcDuXXmglOf9Nhj3&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;K/eiMOE7UxeHy4vNDQyNPfhtGe7zyWqbshV2Gi2l9gyOfjt+sRDFpTG27i4Cc9Du&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;NJ7EBnBIkoyswJOUmdt6YbmuAKEELZGQB9v94hIPzTpa51qbuyjNFQWVDaj2xPC5&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;fmWVCW65uwKBgQDCb0ns0Q1oJzgOo6WGERuWchTMesx6tACuuygAVB51kNlXSOnW&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;xZMESeHXXkuGlskjz5XKQ5QScrPOTmYXVUxd1i9iMuFwmzdfHcDvcBTM+Sgxt3tC&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;3sOkvp7NoZ4ehJo6rtrFJnp3eZ+WSCQGmc6ad6iCRTyk2WPRN0dtitSaqQKBgENi&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;TnQqABGw4auJ/yraetH/228BbztPf0oWlQGRtaMEMUwd+zNeogpTIAHgMzn2Ev5I&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;IQw6ucOf9ORwGQ/0fVm1g3VPFsuOat4xi1oAsYX1fZ74Is+ZJTRHGREzcQVHb/Lt&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;e5fbLtlLnFuPOmjz4ZwyAd/4hA2d2Du8cXWndHzvAoGBAJyuL43KBer234fD9Giu&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;w+1/PR26rSO0rdKLihE2zRn4l/Kv+/UTZxGSLqpNZpGYnxJ0hAJ7J6N5yFFWZwBp&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;vPB5yHmzkMVEYjWgNPzt4WjR3AroYJVzykNEPjdKcBCAM9GA+46W5KbBXbfWavE+&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;amtNmCJjuaiHJJjydyrTSUVB&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;-----END PRIVATE KEY-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"client_email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"groups-retrieval-guide@your-project-id.iam.gserviceaccount.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"104092906165806390865"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://accounts.google.com/o/oauth2/auth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://oauth2.googleapis.com/token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth_provider_x509_cert_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.googleapis.com/oauth2/v1/certs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"client_x509_cert_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.googleapis.com/robot/v1/metadata/x509/groups-retrieval-guide%40your-project-id.iam.gserviceaccount.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"super_user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@slashid.dev"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that done, we can add the credentials to SlashID through our external credentials &lt;a href="https://developer.slashid.dev/docs/api/core/create-a-new-set-of-external-credentials" rel="noopener noreferrer"&gt;API&lt;/a&gt; as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://sandbox.slashid.dev/organizations/config/external-credentials'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-OrgID: &amp;lt;YOUR ORG ID&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-API-Key: &amp;lt;YOUR API KEY&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-raw&lt;/span&gt; &lt;span class="s1"&gt;'{
  "extcred_provider": "google",
  "extcred_type": "json_credentials",
  "extcred_label": "group retrieval credential"
  "json_blob": {
    "type": "service_account",
    "project_id": "your-project-id",
    "private_key_id": "8167c485286f057e06e4a9d17e99ab4913627dbd",
    "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCiaBGNydTgs0WV\numdNqzt5FlqigilYVk4J2oQqimg6Cnf8Q27ChShgDzUfwMbImm65MRFWD8UbiVGs\nZ9Q36bK53HKPgi07sZtdxuuwx2/CZ24l1q1N3Dpv5pfYzm2Z24pJbzWIF1pvDnmp\nZLA0iEXCnvOUIZ2tZ1sYmC7KH1MAbip7LNa6pJ/3fA1dRW7NWvporqFZCaGNsGwM\n0vNtS6dEwayhh8IFh2tCSARXOP62ji3BppTUItqBoh3mqP5L7iTv9iXTKTcTXb9E\nm5U1Espzc//UbElWashstYksTZYa6yrD7p2Zns2T4xRASL7j33dT5uHzIkvfeRdu\nyPj09W9TAgMBAAECggEASBj3IgDl1lL/oza7QYmwv1KjLd2mySaXQlyVq+UB3DJl\njcHJ2+UNRYe6x7vnA4s7eE9GKPSbRlwxu93kImZHB6fL29WoiwWPuZPjcfk3rhAI\noBernBMWhjLSWldZ5KHHxE3wb9geN4svi3m9l7Sfc4TpEWvS+fYWRNbafrRlPpz0\nQoFDSQy9FqxB7tGhs7insl+N16C8eQyjaysQt/xvk7pUQO6tYpVU1RVKIZlzjesU\nvOx2NfZSktILhB4teFYRQbGU0trPDfXOfj/NEQ1TUrL0gARHjYjalyrPgk2DNzXH\nCEx6ImQEKHLiJ9YeOGWvCkMBOb66kstlSwm6PxDf4QKBgQDXbvFKFmco970Yqg79\nuwJkOA29Wtqz+J9RnQd9WGL2wbFk/NCpqDnmbNM7UQm6nS9fhNkNG2BbBbW1gaYG\n+5sQ6X69ctxSVvfBqeFwwSdDqz6VReyGeQO7pNT51Ze4cW7O3BIKEoxicAgrw5uR\nAIUvHKs+UYtipAt0Y/I5GMdbSQKBgQDA/PGsnVdnPHuaPHbdJcDuXXmglOf9Nhj3\nK/eiMOE7UxeHy4vNDQyNPfhtGe7zyWqbshV2Gi2l9gyOfjt+sRDFpTG27i4Cc9Du\nNJ7EBnBIkoyswJOUmdt6YbmuAKEELZGQB9v94hIPzTpa51qbuyjNFQWVDaj2xPC5\nfmWVCW65uwKBgQDCb0ns0Q1oJzgOo6WGERuWchTMesx6tACuuygAVB51kNlXSOnW\nxZMESeHXXkuGlskjz5XKQ5QScrPOTmYXVUxd1i9iMuFwmzdfHcDvcBTM+Sgxt3tC\n3sOkvp7NoZ4ehJo6rtrFJnp3eZ+WSCQGmc6ad6iCRTyk2WPRN0dtitSaqQKBgENi\nTnQqABGw4auJ/yraetH/228BbztPf0oWlQGRtaMEMUwd+zNeogpTIAHgMzn2Ev5I\nIQw6ucOf9ORwGQ/0fVm1g3VPFsuOat4xi1oAsYX1fZ74Is+ZJTRHGREzcQVHb/Lt\ne5fbLtlLnFuPOmjz4ZwyAd/4hA2d2Du8cXWndHzvAoGBAJyuL43KBer234fD9Giu\nw+1/PR26rSO0rdKLihE2zRn4l/Kv+/UTZxGSLqpNZpGYnxJ0hAJ7J6N5yFFWZwBp\nvPB5yHmzkMVEYjWgNPzt4WjR3AroYJVzykNEPjdKcBCAM9GA+46W5KbBXbfWavE+\namtNmCJjuaiHJJjydyrTSUVB\n-----END PRIVATE KEY-----\n",
    "client_email": "groups-retrieval-guide@your-project-id.iam.gserviceaccount.com",
    "client_id": "104092906165806390865",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/groups-retrieval-guide%40your-project-id.iam.gserviceaccount.com",
    "super_user": "user@slashid.dev"
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The credentials are encrypted using envelope encryption backed by an HSM to keep key material safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring OAuth2 credentials
&lt;/h2&gt;

&lt;p&gt;As described in our guide to setup SSO with &lt;a href="https://developer.slashid.dev/docs/guides/SSO/sso_5min/" rel="noopener noreferrer"&gt;SlashID&lt;/a&gt;, once you obtain a &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; for Google you can pass an extra parameter to the API called &lt;code&gt;external_cred&lt;/code&gt; which references the service account credentials we created in the previous step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="s1"&gt;'https://sandbox.slashid.dev/organizations/sso/oidc/provider-credentials'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-OrgID: &amp;lt;YOUR ORG ID&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'SlashID-API-Key: &amp;lt;YOUR API KEY&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-raw&lt;/span&gt; &lt;span class="s1"&gt;'{
    "client_id": "&amp;lt;CLIENT ID&amp;gt;",
    "client_secret": "&amp;lt;CLIENT SECRET&amp;gt;",
    "provider": "google",
    "label": "A label for this set of credentials"
    "external_cred": &amp;lt;EXTERNAL_CRED_ID&amp;gt;
}
'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Now that we have the necessary pieces in place, you can use the &lt;code&gt;requires_groups&lt;/code&gt; parameter in your authentication requests. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;oidc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ux_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uxMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;requires_groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call above will retrieve the Google groups for the user logging in and return them as part of the OIDC token in &lt;code&gt;user.decodedTokenContainer.oidc_tokens&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You are now ready to start using /id to retrieve Google Groups as part of the SSO sign-in flow.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>productivity</category>
      <category>api</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
