<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SlashID Blog</title><description>Protect Every Identity.</description><link>https://slashid.dev/</link><item><title>Achieving Least Privilege: Unused Entitlement Removal</title><link>https://slashid.dev/blog/automatic-least-privilege/</link><guid isPermaLink="true">https://slashid.dev/blog/automatic-least-privilege/</guid><description>Unused entitlements are one of the easiest ways for an attacker to move laterally in a target environment.  However, reducing permissions is often very difficult due to availability concerns and the complexity of the permission systems.  This blog post explores how SlashID solves this problem so that customers can automatically resize identity permissions and achieve least privilege.</description><pubDate>Mon, 05 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Least privilege is a goal that many organizations aim to achieve but rarely do. For example, Microsoft estimates that almost 98% of their tenants have at least one overprivileged identity in their tenants.&lt;/p&gt;
&lt;p&gt;At a fundamental level, the presence of unused permissions leads to a key problem for an organization: once an identity is compromised, it becomes much easier for an attacker to move laterally or escalate privileges in an environment.&lt;/p&gt;
&lt;p&gt;However removing unused permissions is far from easy, so at SlashID we&apos;ve worked hard to automate the process and help companies achieve a safer posture.&lt;/p&gt;
&lt;h2&gt;The issues with achieving least privilege&lt;/h2&gt;
&lt;p&gt;Depending on the organization, several factors get in the way of removing unused entitlements but three are by far the most common:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Concerns over uptime&lt;/strong&gt;: What if removing an entitlement causes a critical cron job to stop functioning or a key employee can&apos;t do their job when it is most needed?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex authorization systems&lt;/strong&gt;: Authorization systems are increasingly harder to comprehend, especially for CSPs. Creating least privilege policies at provisioning is all but impossible&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Birthright creep&lt;/strong&gt;: Permissions are commonly assigned to users based on their job function. This often means that everyone in a given department gets the same permissions irrespective of whether they need them or not&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The SlashID approach to the problem&lt;/h2&gt;
&lt;p&gt;At SlashID we have combined the power of an identity access graph with real-time streaming of audit logs. This combination allows us to identify unused permissions for each identity and automatically generate a new policy to remove them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We do this for all supported environments, not just CSPs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&apos;s an example showing how to generate a policy that removes unused permissions for an AWS identity:&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;padding:59.41% 0 0 0;position:relative;&quot;&amp;gt;&lt;iframe frameborder=&quot;0&quot; allow=&quot;autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media&quot; style=&quot;position:absolute;top:0;left:0;width:100%;height:100%&quot; title=&quot;SlashID Permission resizing&quot; /&gt;&amp;lt;/div&amp;gt;&amp;lt;script src=&quot;https://player.vimeo.com/api/player.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

&lt;/p&gt;
&lt;h2&gt;What about built-in permission analyzers?&lt;/h2&gt;
&lt;p&gt;GCP and AWS provide built-in permissions analyzers that can help identify unused permissions and build least privilege policies; however, they suffer from several shortcomings:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The lookback window is limited to 90 days: if you want to preserve permissions used by an identity sporadically, you can&apos;t leverage the built-in access analyzers&lt;/li&gt;
&lt;li&gt;They are not automated: if you want automated remediation, you need to build a workflow pipeline yourself&lt;/li&gt;
&lt;li&gt;They don&apos;t take into account all events leading to potentially incorrect remediations&lt;/li&gt;
&lt;li&gt;They don&apos;t take into account impersonation: Often roles and service accounts are used by multiple identities so the role itself might require all permissions assigned to it but the identities that can impersonate that role don&apos;t need to. The built-in analyzers don&apos;t see that&lt;/li&gt;
&lt;li&gt;They always create new policies from scratch instead of recommending existing ones that could fit the identity&lt;/li&gt;
&lt;li&gt;Often the cost of generating new policies skyrockets, making this an exercise that can only be done rarely rather than continuously&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With SlashID, those problems are automatically addressed for you so you can safely remove unused permissions without manual effort or downtime.&lt;/p&gt;
&lt;h2&gt;Results you can expect&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;50‑90 % reduction in standing privileges within the first month&lt;/li&gt;
&lt;li&gt;Zero unplanned downtime&lt;/li&gt;
&lt;li&gt;10-30% Saving from unused seats or licenses&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Removing unused permissions is one of the best hygiene measures companies can take to prevent an incident from turning into a breach. Please get in touch to learn &lt;a href=&quot;https://www.slashid.dev/contact/&quot;&gt;more&lt;/a&gt;.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Credential Tokenization: Protecting third-party API credentials</title><link>https://slashid.dev/blog/credential-tokenization/</link><guid isPermaLink="true">https://slashid.dev/blog/credential-tokenization/</guid><description>Stolen secrets and credentials are one of the most common ways for attackers to move laterally and maintain persistence in cloud environments.  In this blog post we introduce credential tokenization to protect secrets at runtime, introduce separation of duties, and reduce the credential rotation burden.</description><pubDate>Mon, 10 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Stolen secrets and credentials are one of the most common ways for attackers to move laterally and maintain persistence in cloud environments.&lt;/p&gt;
&lt;p&gt;Modern cloud deployments employ secrets management systems such as KMS to protect key materials at rest and avoid leaking keys or credentials in source code or other build artifacts. However, secrets are unprotected at runtime, so any vulnerability or compromise of a service could lead to credential theft.&lt;/p&gt;
&lt;p&gt;In this blog post, we introduce credential tokenization to protect secrets at runtime, introduce separation of duties, and reduce the credential rotation burden.&lt;/p&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;Third-party non-human identities and machine credentials such as API keys, access tokens, and OAuth 2.0 client credentials have three key challenges to address: - &lt;strong&gt;Absence of lifecycle management&lt;/strong&gt;: no standardized way to manage the provisioning and de-provisioning of these identities - &lt;strong&gt;Lack of security protections&lt;/strong&gt;: It’s not possible to enforce security mitigations such as step-up authentication and MFA on NHI due to their nature as non-interactive credentials - &lt;strong&gt;Complex remediation&lt;/strong&gt;: Revoking or rotating these credentials is often slow and cross-functional because it can result in downtime&lt;/p&gt;
&lt;p&gt;Further, these credentials are fully exposed at runtime even in the presence of a secret manager because the application can access the key material or secret directly.&lt;/p&gt;
&lt;h2&gt;Our approach&lt;/h2&gt;
&lt;p&gt;Similar to the Fly.io security team, we have adopted a paradigm we call Credential Tokenization. The idea is similar to what payment processors do with credit card numbers: minimize exposure and replace the card number with a token.&lt;/p&gt;
&lt;p&gt;The way we do this is through a &lt;a href=&quot;https://developer.slashid.dev/docs/gate&quot;&gt;Gate&lt;/a&gt; plugin, specifically, Gate injects secrets on outgoing requests and the application itself never touches key material.&lt;/p&gt;
&lt;h2&gt;Benefits&lt;/h2&gt;
&lt;p&gt;Our approach provides several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reduced Key Material Exposure&lt;/strong&gt;: By keeping secrets out of your application code and configuration files, you reduce the risk of accidental exposure or leaks. Further, even if the application is compromised that won&apos;t lead to secret leakage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enforce fine-grained access control&lt;/strong&gt;: Gate&apos;s Tokenizer plugin can be combined with our OPA or OAuth 2.0 plugins to enforce fine-grained access control policies for secret retrieval and injection, especially useful when the secret itself doesn&apos;t have RBAC/roles restriction.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Separation of duty and easier rotation&lt;/strong&gt;: Developers don’t have to worry about credentials lifecycle and management. Credentials can be rotated without downtime.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;h3&gt;Topology&lt;/h3&gt;
&lt;p&gt;Gate can be deployed in many different topologies: as a sidecar, as an external authorizer, a lambda authorizer, and more. For simplicity in this example we&apos;ll assume a sidecar deployment, that looks like the picture below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/credential-tokenization/sidecar.png&quot; alt=&quot;sidecar&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Workflow&lt;/h3&gt;
&lt;p&gt;Here&apos;s a high-level overview of how credential tokenization works with SlashID and Gate:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Secret Creation&lt;/strong&gt;: Use the secret generator utility to create a secret with the desired configuration. The utility allows you to specify the secret engine (e.g., SlashID, AWS Secrets Manager, GCP Secret Manager), the injection method (e.g., header injection, HMAC injection), and other relevant settings. The secret is then stored in the chosen secret engine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Application Request&lt;/strong&gt;: When your application needs to make a request to an external service that requires a secret (e.g., an API key), it sends the request to the Gate proxy instead of directly to the service. The request includes a special header (e.g., &lt;code&gt;SID_Proxy_Auth&lt;/code&gt;) that contains the secret identifier.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Secret Retrieval&lt;/strong&gt;: Gate&apos;s Tokenizer plugin intercepts the request and extracts the secret identifier from the &lt;code&gt;SID_Proxy_Auth&lt;/code&gt; header. It then makes a request to the secret manager to retrieve the corresponding secret using the secret identifier.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Secret Injection&lt;/strong&gt;: Once the Tokenizer plugin receives the decrypted secret from SlashID, it injects the secret into the request based on the specified configuration. This can involve setting a header value, computing an HMAC signature, or injecting an OAuth token.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Request Forwarding&lt;/strong&gt;: After injecting the secret, Gate forwards the modified request to the destination service. The service receives the request with the required secret, processes it, and sends the response back to Gate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Response Handling&lt;/strong&gt;: Gate receives the response from the service and forwards it back to your application. The application receives the response without any knowledge of the tokenization process that occurred behind the scenes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Request Handling in a picture&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/blog/credential-tokenization/req-handling.svg&quot; alt=&quot;Request Handling&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Gate configuration&lt;/h3&gt;
&lt;p&gt;The Gate configuration is straightforward as well, as a simple example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gate:
---
plugins:
---
- id: tokenize_requests
  type: tokenizer
  enabled: true
  parameters:
    header_with_proxy_token: SID_Proxy_Auth
    secret_engine: slashid
---
default:
  transparent: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;header_with_proxy_token&lt;/strong&gt; specifies the header that contains the encrypted secret identifier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;secret_engine&lt;/strong&gt; is set to &quot;slashid&quot;, indicating that SlashID is being used as the secret engine. Currently, we support Hashicorp Vault, AWS Secret Manager, GCP Secret Manager, and Azure Key Vault.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this configuration, Gate&apos;s Tokenizer plugin will intercept requests, retrieve the corresponding secret from SlashID based on the secret ID in the SID_Proxy_Auth header, and inject it into the request based on the secret&apos;s configuration.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;transparent&lt;/code&gt; mode for Gate means that no URL rewriting occurs and the request is simply forwarded to the remote server.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Through credential tokenization, you can minimize the handling of key material, enforce fine-grained access control on API keys, and make credential lifecycle management significantly easier.&lt;/p&gt;
&lt;p&gt;Please &lt;a href=&quot;https://www.slashid.dev/contact/&quot;&gt;contact us&lt;/a&gt; if you are interested in securing your third-party credentials.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>App-layer cryptographic primitives for secure storage of user data</title><link>https://slashid.dev/blog/app-layer-encryption/</link><guid isPermaLink="true">https://slashid.dev/blog/app-layer-encryption/</guid><description>In this blogpost we explore the cryptographic primitives and design decisions we made building our Data Vault module. Our service is a globally replicated, field-level encrypted, data store to keep user data safe and compliant with Data Protection laws while improving UX by decreasing latency through data locality.</description><pubDate>Thu, 20 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;Since SlashID’s inception, our goal has been to give developers a secure way to store user data without hosting it on their own internal infrastructure or worrying about how to do cryptography right.&lt;/p&gt;
&lt;p&gt;Furthermore, for better or worse, the regulatory environment around user data was becoming increasingly strict, so we aimed to build a data storage engine that was not only safe but also complied by design with data protection and localization regulations mandated by various jurisdictions.&lt;/p&gt;
&lt;p&gt;Given our premises, we had a number of requirements for our data encryption layer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Database-agnostic&lt;/li&gt;
&lt;li&gt;Serverless&lt;/li&gt;
&lt;li&gt;Handles encryption and data localization transparently&lt;/li&gt;
&lt;li&gt;Hardware-based root of trust to implement crypto-anchoring and avoid mass data exfiltration&lt;/li&gt;
&lt;li&gt;Data can be either globally replicated or localized to an individual region&lt;/li&gt;
&lt;li&gt;Compliance with GDPR and similar regulations by (i) keeping an audit log of data access requests and (ii) using crypto-shredding to service data deletion requests&lt;/li&gt;
&lt;li&gt;No noticeable latency for the end user&lt;/li&gt;
&lt;li&gt;Ability to perform encrypted searches with equality&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the following sections we’ll describe the architecture of our storage encryption layer and the primitives used to build it.&lt;/p&gt;
&lt;p&gt;Before moving to the technical details, we’d like to thank Dino Dai Zovi and Sam Quigley whose work on the Square’s infrastructure has been an inspiration for our own design. Dino and his team have written about parts of their infrastructure in this Cash App &lt;a href=&quot;https://code.cash.app/app-layer-encryption&quot;&gt;post&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;A better Developer Experience&lt;/h2&gt;
&lt;p&gt;We are engineers first and foremost and it was important for us to build a product developers genuinely want to use because it simplifies their day to day job. In the context of authentication and identity management, this translates into building a platform that allows developers to stop spending cycles to implement and worry about encryption, data localization or key management and rotation.&lt;/p&gt;
&lt;p&gt;From the outside, SlashID’s interface looks like a standard REST API, coupled with a set of SDKs, both client-side and server-side, that together allow developers to store and retrieve users and their associated data.&lt;/p&gt;
&lt;p&gt;For instance, this is a code snippet that shows how to register/sign-in an user and how to store and retrieve encrypted attributes using our client-side JavaScript SDK:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var sid = new slashid.SlashID({ oid: Organization_ID })
// ... initialization code ...

const user = await sid.id(
  Organization_ID,
  {
    type: &apos;phone_number&apos;,
    value: &apos;+13337777777&apos;,
  },
  {
    method: &apos;webauthn&apos;,
  }
)
// ... authentication logic ...

await user.set({ attr1: &apos;value1&apos; })

const attrs = await user.get()
// =&amp;gt; attrs === {&quot;attr1&quot;: &quot;value1&quot;, ...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example above, we are registering/logging in a user by the phone number &lt;code&gt;+13337777777&lt;/code&gt; as handle, and using &lt;code&gt;webauthn&lt;/code&gt; as the authentication method. By default, if the user connected to the website from Lisbon their data would be automatically stored in the EU, if they connected from New York the data would be stored in the US - you can override that choice in the &lt;code&gt;id()&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;Calling &lt;code&gt;set&lt;/code&gt; on a user creates a new attribute in our Data Vault module. When that happens an attribute-specific key is generated and is used to encrypt &lt;code&gt;value1&lt;/code&gt;. The key is itself encrypted with a user-specific key generated at user-creation time.&lt;/p&gt;
&lt;p&gt;Calling &lt;code&gt;get&lt;/code&gt; on a user allows you to retrieve one or more attributes associated with the user. The attributes are decrypted and returned in clear-text to the caller.&lt;/p&gt;
&lt;p&gt;Note how, throughout the example, you don&apos;t have to worry about encryption nor data localization and the user data never touches your own infrastructure.&lt;/p&gt;
&lt;h2&gt;Overall architecture&lt;/h2&gt;
&lt;p&gt;We made the decision that our encryption layer would be database-agnostic. This was done because we want the possibility to expand datastore coverage beyond our current database if needed.&lt;/p&gt;
&lt;p&gt;Some data structures in our architecture are encrypted and globally replicated while others are encrypted but localized to a specific region.&lt;/p&gt;
&lt;p&gt;We make extensive use of &lt;a href=&quot;https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#enveloping&quot;&gt;envelope encryption&lt;/a&gt;. Our chain of trust forms a tree as depicted below, each child key is encrypted with the parent key.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/app-layer-encryption/tree.png&quot; alt=&quot;SlashID Key Tree&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Properties of the key hierarchy&lt;/h3&gt;
&lt;p&gt;The key hierarchy mentioned above is generated per-region and this structure brings a number of advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A compromised key doesn’t compromise all the data&lt;/li&gt;
&lt;li&gt;Deleting user data can be achieved through &lt;a href=&quot;https://en.wikipedia.org/wiki/Crypto-shredding&quot;&gt;crypto-shredding&lt;/a&gt; by deleting the corresponding user key&lt;/li&gt;
&lt;li&gt;Data can be replicated globally without violating data residency requirements since the keys are localized per-region&lt;/li&gt;
&lt;li&gt;The root of trust is stored in a Hardware Security Module (HSM) making a whole-DB compromise an extremely arduous task&lt;/li&gt;
&lt;li&gt;We can enforce fine-grained access control on the HSM to tightly control and reduce the number of services able to decrypt subtrees&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Keys lifecycle&lt;/h3&gt;
&lt;p&gt;Keys are created on demand when a new org, a new user or a new attribute is generated. Keys are never stored unencrypted anywhere at rest and only one service within SlashID’s infrastructure has the ability to access the HSM to decrypt the master key, significantly reducing the chances of compromise.&lt;/p&gt;
&lt;p&gt;Derived keys are stored in a database that is encrypted, geo-localized and backed-up every 24 hours.&lt;/p&gt;
&lt;p&gt;Lastly, all keys are rotated at regular, configurable, intervals.&lt;/p&gt;
&lt;h2&gt;The primitives&lt;/h2&gt;
&lt;p&gt;We spent a significant amount of time evaluating the correct set of primitives to use for our architecture. Below are some of the key decisions we made.&lt;/p&gt;
&lt;h3&gt;HSM selection&lt;/h3&gt;
&lt;p&gt;At this point, we don’t have a dedicated data center at SlashID, so we evaluated various solutions provided by external cloud vendors.&lt;/p&gt;
&lt;p&gt;We excluded Azure right away because it doesn’t support symmetric keys unless using managed HSMs. Azure is also not supported by our cryptographic library of choice, Tink.&lt;/p&gt;
&lt;p&gt;There’s minimal difference in features or cost between GCP and AWS, but we ended up picking GCP because of two key features:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GCP’s External Key Manager (EKM) supports the use of external HSM vendors out of the box, allowing our customers to pick an HSM vendor of their choice and to manage their own keys.&lt;/li&gt;
&lt;li&gt;GCP is certified &lt;a href=&quot;https://en.wikipedia.org/wiki/FIPS_140-2#Level_3&quot;&gt;FIPS 140-2&lt;/a&gt; Level 3, whereas AWS is FIPS 140-2 Level 2, hence GCP has stronger security guarantees.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Cryptographic library&lt;/h3&gt;
&lt;p&gt;When implementing any cryptographic system, selecting the correct library is paramount. Our library of choice is Google &lt;a href=&quot;https://developers.google.com/tink&quot;&gt;Tink&lt;/a&gt;. This &lt;a href=&quot;https://github.com/google/tink/blob/master/docs/Tink-a_cryptographic_library--RealWorldCrypto2019.pdf&quot;&gt;presentation&lt;/a&gt; does a great job of explaining Tink’s design principles and why it’s a superior choice when compared to better known libraries such as OpenSSL.&lt;/p&gt;
&lt;p&gt;For our Data Vault module, we were looking for a few key features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Support for deterministic authenticated encryption&lt;/li&gt;
&lt;li&gt;Native Golang support since that’s the primary language our backend is written in&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tink also has a few more unique features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tink manages nonces directly, preventing mistakes in the implementation or usage of cryptographic algorithms, which could easily result in vulnerabilities&lt;/li&gt;
&lt;li&gt;Tink has an interface to talk directly to HSMs hosted in GCP and AWS, reducing the burden of managing the KMS/Cloud HSM APIs&lt;/li&gt;
&lt;li&gt;Tink reduces key rotation complexity through &lt;a href=&quot;https://developers.google.com/tink/design/keysets&quot;&gt;keysets&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While Tink is not the fastest cryptographic library available today, it is the easiest to use safely and, as we’ll see below, the performance overhead is still negligible for the algorithms we adopt.&lt;/p&gt;
&lt;h3&gt;Key Types and Encryption Modes&lt;/h3&gt;
&lt;p&gt;We employ a variety of encryption methods depending on the data type. Tink provides a long list of &lt;a href=&quot;https://developers.google.com/tink/supported-key-types&quot;&gt;supported types&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At a high level, we employ deterministic encryption for data on which we perform encrypted search. For most of the remaining data, we use AES-GCM encryption, which has hardware &lt;a href=&quot;https://en.wikipedia.org/wiki/AES_instruction_set&quot;&gt;support&lt;/a&gt; on most modern processors and generally faster performance without sacrificing security. If you want to read more, &lt;a href=&quot;https://soatok.blog/2020/07/12/comparison-of-symmetric-encryption-methods/&quot;&gt;this blogpost&lt;/a&gt; does a great job at comparing symmetric encryption methods.&lt;/p&gt;
&lt;p&gt;Note that, strictly speaking, AES-GCM-SIV and XChaCha20-Poly1305 would be safer choices because they either have longer nonces or prevent nonce abuse.
However, AES-GCM-SIV comes with a ~30% performance hit, which we considered unacceptable for our use case, and, as mentioned earlier, Tink also implements safe nonce management.&lt;/p&gt;
&lt;p&gt;As for XChaCha20-Poly1305, it is cryptographically stronger than GCM because it has a longer nonce. However, its performance is worse compared to AES-GCM due to the lack of built-in hardware instruction sets. We overcome that risk by not reusing keys and by letting Tink handle nonce management.&lt;/p&gt;
&lt;h3&gt;Secure enclaves or not?&lt;/h3&gt;
&lt;p&gt;Both AWS and GCP provide support for confidential computing. In AWS, trusted environments go under the commercial name of Nitro Enclaves, whereas in GCP they are called Confidential VMs.&lt;/p&gt;
&lt;p&gt;The principle behind both products is to provide an isolated environment similar to what HSMs provide, but using Virtual Machines instead of hardware.&lt;/p&gt;
&lt;p&gt;Ultimately, we decided against using Confidential VMs or Nitro Enclaves due to the very limited security benefits they provide. To understand why, let’s analyze Nitro Enclaves briefly; very similar considerations apply to GCP’s Confidential VMs.&lt;/p&gt;
&lt;p&gt;Nitro Enclaves are implemented by partitioning the CPU and memory of a single “parent” EC2 instance to run a Docker container in them. While a container can normally perform arbitrary computation, when executed within a Nitro Enclave its only external communication channel is a single socket connection via &lt;a href=&quot;https://aws.amazon.com/blogs/aws/aws-nitro-enclaves-isolated-ec2-environments-to-process-confidential-data/&quot;&gt;Virtual Sockets&lt;/a&gt;, while the Enclave runs a nested, secure, hypervisor.
From the outside, you can&apos;t see any internal console messages, logs or access the container in any way. The only visible data is the input and output of the sockets.
Further, only the parent instance (i.e.,the EC2 that created the enclave), can communicate with the Docker container running inside the enclave.&lt;/p&gt;
&lt;p&gt;The enclave created in this way has two key features:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The enclave can speak directly to AWS’ Key Manager Service (KMS) over TLS. Hence, data encryption/decryption can be achieved directly from the Docker container inside the Nitro Enclave without talking to the parent instance.&lt;/li&gt;
&lt;li&gt;The enclave generates an attestation of the Docker container running on it. This attestation allows you to create access rules within the KMS, so that only enclaves with a particular hash (i.e., a specific pre-agreed Docker image, running pre-agreed code) can decrypt data.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While these two properties have high value, lambda functions within GCP and AWS provide similar isolation features and significantly lower lifetime, reducing the burden and risk of maintaining an EC2 instance.&lt;/p&gt;
&lt;p&gt;We evaluated whether the benefits of KMS Key Policy attestation were valuable enough to justify the extra overhead, and, in the end, determined that it was not a favorable tradeoff. This is primarily because KMS policies are governed by IAM roles in AWS, so they are not a silver bullet in and of themselves: an attacker with access to IAM can change the KMS policies and swiftly invalidate any advantage of Nitro Enclaves with respect to access control.&lt;/p&gt;
&lt;p&gt;So while Nitro Enclaves offer marginally more protections than standard instances, we think that that protection is offset by the additional complexity and the longer lifespan of the EC2 instance running the enclave.&lt;/p&gt;
&lt;p&gt;In the end, for our use case, an adequate IAM policy coupled with serverless execution provides a strong and comparable safe alternative.&lt;/p&gt;
&lt;h2&gt;Performance impact&lt;/h2&gt;
&lt;p&gt;As we already discussed, algorithm selection was a key consideration for us, both in terms of security and speed. To test the impact of encryption on performance, we ran our service with and without encryption.&lt;/p&gt;
&lt;p&gt;Our benchmarks indicate that the overhead of our encryption layer vs clear-text for data retrieval is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BenchmarkDecryptAttributes_1b-10        	   65793	     17894 ns/op	    9440 B/op	     146 allocs/op
BenchmarkDecryptAttributes_4b-10        	   72162	     18019 ns/op	    9456 B/op	     146 allocs/op
BenchmarkDecryptAttributes_64b-10       	   68924	     17904 ns/op	    9696 B/op	     146 allocs/op
BenchmarkDecryptAttributes_1024b-10     	   59560	     20180 ns/op	   13536 B/op	     146 allocs/op
BenchmarkDecryptAttributes_4096b-10     	   48620	     24793 ns/op	   25824 B/op	     146 allocs/op
BenchmarkDecryptAttributes_65536b-10    	   13249	     89876 ns/op	  263393 B/op	     146 allocs/op
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We believe that ~0.08 milliseconds for a 64k block is an acceptable result for our needs and the sub-linear growth in latency means that we would be able to process even significantly larger attributes with minimal performance impact.&lt;/p&gt;
&lt;h2&gt;Threat model&lt;/h2&gt;
&lt;p&gt;When we built Data Vault, our aim was to create an exfiltration-resistant, fast and compliant module for our customers.&lt;/p&gt;
&lt;p&gt;The threat we care the most about is an attacker compromising our infrastructure and exfiltrating customer data in bulk.
Our security posture is an evolution of the setup described a few years ago by Diogo Monica and Nathan McCauley from Anchorage and Square in this &lt;a href=&quot;https://blog.diogomonica.com/2017/10/08/crypto-anchors-exfiltration-resistant-infrastructure/&quot;&gt;blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In our system, data exfiltration attacks are prevented thanks to three design choices:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The root of trust is an HSM and only a single service is able to talk to it&lt;/li&gt;
&lt;li&gt;Each attribute and each user are encrypted with a separate, individual key, and therefore the loss of key doesn’t result in loss of other data&lt;/li&gt;
&lt;li&gt;We use encryption search to minimize the encryption/decryption operations performed in our environment, reducing the exposure of keys&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While our architecture doesn’t entirely rule out the risk of mass-exfiltration, it reduces the attack scenarios to one of two cases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The attacker is able to compromise the service allowed to speak with the HSM by obtaining full code execution, and exploit that to decrypt the org keys&lt;/li&gt;
&lt;li&gt;The attacker has compromised our IAM system and has diverted HSM access to a third-party service that is attacker-controlled&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We employ a number of additional countermeasures to further reduce the two risks above. These measures are probabilistic in nature and hence we prefer not to discuss them publicly.&lt;/p&gt;
&lt;p&gt;Comparing Data Vault with the standard database encryption options makes it clear how Data Vault is significantly more secure than other storage alternatives. In fact, in a traditional environment, obtaining access to the database is all the attacker needs in order to exfiltrate or alter customer data – a significantly easier attack path.&lt;/p&gt;
&lt;h2&gt;Disaster recovery&lt;/h2&gt;
&lt;p&gt;Given our security posture, it is key to prevent accidental deletion of encryption keys. We maintain a robust disaster recovery posture through the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HSM keys are only scheduled for destruction with a 90 days grace period.&lt;/li&gt;
&lt;li&gt;Derived keys are stored and encrypted in a database that is replicated and backed up every 24 hours&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;End-to-End Encryption&lt;/h2&gt;
&lt;p&gt;Our current threat model protects user data from malicious third-parties but doesn’t prevent the organization the user belongs to or SlashID from accessing the data.&lt;/p&gt;
&lt;p&gt;Sometimes, it is desirable for the user to have data that can only be accessed by the user and no other third parties, including SlashID. For instance, Flo is offering &lt;a href=&quot;https://flo.health/flo-health-inc/news/anonymous-mode-whitepaper&quot;&gt;anonymous mode&lt;/a&gt; to users who don’t want to share their information after the Roe vs. Wade ruling.&lt;/p&gt;
&lt;p&gt;In a separate blogpost we’ll describe our approach to E2E Encryption of user attributes and how our customers can leverage it to provide stronger security guarantees to their users.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this blogpost we’ve shown the technical decisions we made to build our secure user storage module, Data Vault.&lt;/p&gt;
&lt;p&gt;The state of cryptographic primitives has improved dramatically in the last few years, making building a system like ours a more feasible task. In particular:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Globally available Crypto Anchors wouldn’t be feasible without Cloud HSMs due to the DevOps overhead of running your own HSMs in a data center.&lt;/li&gt;
&lt;li&gt;Field-level encryption wouldn’t be feasible without hardware support for certain primitives such as AES due to the performance impact&lt;/li&gt;
&lt;li&gt;The risk of misusing cryptographic libraries/primitives would be a lot higher without Tink&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notwithstanding the leap forward in cryptographic primitives, there are many intricacies and gotchas to maintaining such a system, and a single blogpost can’t cover all of them in detail. We plan to describe in a series of blog posts some of the more gnarly aspects of building Data Vault.&lt;/p&gt;
&lt;p&gt;If you have built a similar project internally or plan to, we would love to compare notes! Please drop us a line.&lt;/p&gt;
</content:encoded><author>Giovanni Gola, Vincenzo Iozzo</author></item><item><title>Backend Authentication and Authorization Patterns: Benefits and Pitfalls of Each</title><link>https://slashid.dev/blog/auth-patterns/</link><guid isPermaLink="true">https://slashid.dev/blog/auth-patterns/</guid><description>Identity in distributed applications is hard. In large and complex environments with multiple services, a number of patterns have emerged to authenticate and authorize traffic.  In this article, we’ll discuss the most common ones, how to implement them, and their pros and cons.</description><pubDate>Thu, 28 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Identity in distributed applications is hard. It&apos;s a complex matter and depending on what you are building, implementation varies. In partcular, once your application moves away from a monolith to microservices or serverless the authentication and authorization plane that once served as middleware in the monolith has adapted to a number of different patterns.&lt;/p&gt;
&lt;p&gt;In this article, we&apos;ll cover some of the most common ones in use. Note that these patterns are agnostic to the authentication/authorization mechanism used, so we won&apos;t touch on those in this post.&lt;/p&gt;
&lt;h2&gt;The API Gateway/edge authentication pattern&lt;/h2&gt;
&lt;h3&gt;What it is&lt;/h3&gt;
&lt;p&gt;At a high level, the API Gateway/edge serves as the termination point for token inspection, enrichment, and payload encryption/decryption. In this scenario, the edge authentication service is responsible for inspecting the tokens provided by the client and running one or more verification and modification passes on it before it reaches the end service.&lt;/p&gt;
&lt;p&gt;There are many derived architectures from this base pattern, and some of the more common ones are noted below (some of these can be combined):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The token used by the client is translated to an internal &quot;secure&quot; token, such that an attacker has fewer chances to craft a valid internal token. For instance, the internal token might be signed with a key pair that is not exposed to the public.&lt;/li&gt;
&lt;li&gt;The edge authentication service reaches out to an authorization server that enriches the token with scopes/roles used to enforce authorization decisions at the microservice level.&lt;/li&gt;
&lt;li&gt;The token propagated internally is a &quot;passport,&quot; which is a way to propagate the client identity information in a uniform way, especially in environments with multiple IdPs or token types.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A further variation to this is the Backend for Frontend (BFF) pattern. From an AuthN/AuthZ perspective, the BFF pattern doesn&apos;t significantly change the logic but adds further complexity in maintaining states and logic across different gateways.&lt;/p&gt;
&lt;h3&gt;Benefits&lt;/h3&gt;
&lt;p&gt;The edge authentication pattern is advantageous in scenarios where there are multiple teams utilizing different programming languages and where there is a desire for high performance and a clear separation of responsibilities between infrastructure and application developers.&lt;/p&gt;
&lt;p&gt;This pattern is adopted by several companies, including &lt;a href=&quot;https://netflixtechblog.com/edge-authentication-and-token-agnostic-identity-propagation-514e47e0b602?gi=fe882244dbb1&quot;&gt;Netflix&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Drawbacks&lt;/h3&gt;
&lt;p&gt;This model poses two primary drawbacks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Lower Security Guarantees&lt;/strong&gt;: An attacker that manages to compromise an internal service can move laterally to the rest of the system unchecked, especially in modern environments where the perimeter is at L7 instead of L4.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stale Authorization Decisions&lt;/strong&gt;: This pattern also falters in scenarios where authorization decisions need immediate enforcement, as tokens carrying authorization scopes could become stale.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The middleware pattern&lt;/h2&gt;
&lt;h3&gt;What it is&lt;/h3&gt;
&lt;p&gt;In this pattern, all the AuthN/AuthZ logic is enforced at the service level via middleware. Here, the authorization logic is housed in the middleware, while the authorization data is usually accessed directly from a database.&lt;/p&gt;
&lt;p&gt;The middleware pattern used to be popular as it facilitated the simplest one-to-one migration from a monolith; however, it is becoming increasingly less common today.&lt;/p&gt;
&lt;h3&gt;Benefits&lt;/h3&gt;
&lt;p&gt;The primary benefit of this pattern is that it often represents a 1:1 migration from the monolith. A secondary benefit is that, since AuthN/AuthZ is close to the application logic, compromising a service doesn&apos;t necessarily lead to a breach of the rest of the deployment.&lt;/p&gt;
&lt;h3&gt;Drawbacks&lt;/h3&gt;
&lt;p&gt;This model presents several drawbacks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Divergent Implementations&lt;/strong&gt;: As the environment’s complexity increases, there can be a proliferation of middleware with potentially different behaviors, leading to correctness issues and potential security concerns.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex Migrations&lt;/strong&gt;: Any alterations to the structure of the tokens or the AuthN/AuthZ logic necessitate modifications to each middleware implementation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of Governance/Inspectability&lt;/strong&gt;: Embedding AuthN/AuthZ logic in a DB/middleware renders readability and inspectability unfeasible in organizations where security or legal teams need to inspect such logic.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The embedded pattern&lt;/h2&gt;
&lt;h3&gt;What is it&lt;/h3&gt;
&lt;p&gt;Similar to the middleware pattern, in this pattern, all authentication and authorization are embedded directly in the application code.&lt;/p&gt;
&lt;p&gt;This pattern is a less clean yet even easier way to perform one-to-one migration from a monolith; however, it is strictly inferior to the other approaches due to potential security concerns, particularly when dealing with authentication.&lt;/p&gt;
&lt;h3&gt;Benefits&lt;/h3&gt;
&lt;p&gt;The primary benefit of this pattern is that it often represents a 1:1 migration from the monolith. A secondary benefit is that the authorization logic can be embedded in the database tables, increasing performance.&lt;/p&gt;
&lt;h3&gt;Drawbacks&lt;/h3&gt;
&lt;p&gt;This model is probably the least viable one in real world environments for several reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Divergent Implementations&lt;/strong&gt;: As the environment’s complexity increases, there can be a proliferation of middleware with potentially different behaviors, leading to correctness issues and potential security concerns.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex Migrations&lt;/strong&gt;: Any alterations to the structure of the tokens or the AuthN/AuthZ logic necessitate modifications to each middleware implementation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of Governance/Inspectability&lt;/strong&gt;: Embedding AuthN/AuthZ logic in DB/middleware renders readability and inspectability unfeasible in organizations where security or legal teams need to inspect such logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Higher risk of vulnerabilities&lt;/strong&gt;: Co-mingling application logic with identity logic leads to a significantly higher risk of security vulnerabilities.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The sidecar pattern&lt;/h2&gt;
&lt;h3&gt;What it is&lt;/h3&gt;
&lt;p&gt;A sidecar is a recognized pattern where functionalities of an application are segregated into separate processes to provide isolation and encapsulation. A critical point to note here is that the sidecar and the application coexist in an encapsulated, trusted environment. Thus, all communications between an application and a sidecar are trusted.&lt;/p&gt;
&lt;p&gt;This arrangement facilitates the offloading of authorization and authentication to the sidecar, allowing the incoming connection to pass through the application only after successful authorization and authentication.&lt;/p&gt;
&lt;p&gt;This pattern is gaining popularity, with several companies like &lt;a href=&quot;https://www.youtube.com/watch?v=-40Dr7tpalU&quot;&gt;Block&lt;/a&gt; to &lt;a href=&quot;https://www.jpmorgan.com/technology/technology-blog/protecting-web-applications-via-envoy-oauth2-filter&quot;&gt;JP Morgan&lt;/a&gt; adopting this architecture.&lt;/p&gt;
&lt;p&gt;Envoy has recently released an extension to make the adoption of the sidecar pattern significantly easier in a service &lt;a href=&quot;https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ext_authz_filter#arch-overview-ext-authz&quot;&gt;mesh&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Benefits&lt;/h3&gt;
&lt;p&gt;This model offers several benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Zero-Trust Approach&lt;/strong&gt;: Each service enforces its authentication and authorization logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uniform Implementation&lt;/strong&gt;: A singular implementation is applied to every service, preventing diverging implementations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easier Migrations and Governance&lt;/strong&gt;: Changes to logic or data structure can be centralized in a single place.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Stale Identity data&lt;/strong&gt;: Authorization decisions are executed at the sidecar level, reducing concerns about staleness.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separation of concerns&lt;/strong&gt;: The application development team doesn&apos;t have to worry about the AuthN/AuthZ fabric of the system.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Drawbacks&lt;/h3&gt;
&lt;p&gt;The sidecar approach has two notable drawbacks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Management overhead&lt;/strong&gt;: In this pattern there are more containers to monitor&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency&lt;/strong&gt;: Checking signatures/policies at every microservice introduces increased latency compared to &quot;check once&quot;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Implement the API server and sidecar patterns with Gate&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.slashid.dev/products/gate&quot;&gt;SlashID Gate&lt;/a&gt; supports several deployment modes and can be deployed in conjunction with all API gateways and service meshes.&lt;/p&gt;
&lt;p&gt;We&apos;ll use Kubernetes clusters in these examples, but we also support out-of-the-box integrations with all major cloud vendors’ API gateways to support managed infrastructure.&lt;/p&gt;
&lt;h3&gt;Caching&lt;/h3&gt;
&lt;p&gt;As we discussed earlier in the article, one of the key considerations when deciding on the architecture is latency. Gate has a built-in caching plugin that allows us to pick and choose which URLs and plugins are allowed to cache responses and also allows us to manually override the Cache-Control policy for some URLs.&lt;/p&gt;
&lt;p&gt;The cache is an LRU cache whose size is configurable, with a &lt;strong&gt;default size of 32 MB&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;As an example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;plugins_http_cache:
  - pattern: &apos;*&apos;
    cache_control_override: private, max-age=600, stale-while-revalidate=300
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can find the full configuration &lt;a href=&quot;https://developer.slashid.dev/docs/gate/getting-started/configuration/plugin-config#caching&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Passporting tokens&lt;/h3&gt;
&lt;p&gt;We&apos;ve shown earlier that passporting tokens to propagate an internal token compared to an external one is a common practice used by multiple companies.
Gate supports the ability to remint tokens and to add custom claims to existing tokens.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/auth-patterns/remint.png&quot; alt=&quot;Remint&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When a request comes in, Gate sends a &lt;code&gt;POST&lt;/code&gt; request to the endpoint with the following format to the reminter webhook:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;token&quot;: &quot;Token from the original request&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A valid response looks as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;headers_to_set&quot;: {
    &quot;Authorization&quot;: &quot;Basic YWxhZGRpbjpvcGVuc2VzYW1l&quot;,
    &quot;IsTranslated&quot;: &quot;true&quot;
  },
  &quot;cookies_to_add&quot;: {
    &quot;X-Internal-Auth&quot;: &quot;9xUromfTraIwHpmC6R9NDwJwItE&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, Gate will strip the original token from the request before forwarding. You can instruct Gate to keep the original token by setting the &lt;code&gt;keep_old_token&lt;/code&gt; option to true. Gate will override the original request headers with the headers returned from &lt;code&gt;headers_to_set&lt;/code&gt;, and will add all cookies from &lt;code&gt;cookies_to_add&lt;/code&gt; to the ones already present in the request.&lt;/p&gt;
&lt;h3&gt;The sidecar pattern&lt;/h3&gt;
&lt;p&gt;For this example, we&apos;ll show an example of Gate deployed as an Envoy External Authorization gRPC filter. To simplify the deployment, we&apos;ll use Emissary-ingress as an example, but a similar configuration works for Envoy.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can see a full deployment guide &lt;a href=&quot;https://developer.slashid.dev/docs/gate/guides/ambassador_k8s&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At a high level, there are three key operations to the deployment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Deploying Gate as a service&lt;/li&gt;
&lt;li&gt;Deploy a &lt;code&gt;Filter&lt;/code&gt; and &lt;code&gt;FilterPolicy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Deploy an &lt;code&gt;AuthService&lt;/code&gt; for Emissary&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Conceptually, the deployment looks as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/auth-patterns/sidecar.png&quot; alt=&quot;sidecar&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Having done that, you can configure Gate as you normally would, but specifying the &lt;code&gt;mode&lt;/code&gt; option in the deployment:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mode: ext_auth
default:
  ext_auth_response: deny
port: 5000
tls:
  enabled: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here on, routes and &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins&quot;&gt;plugins&lt;/a&gt; are defined as usual:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Define shared config
slashid_config: &amp;amp;slashid_config
  slashid_org_id: &quot;your_org_id&quot;
  slashid_api_key: &quot;your_api_key&quot;

plugins:
  - id: authn_proxy
    type: authentication-proxy
    enabled: false
    parameters:
      &amp;lt;&amp;lt;: *slashid_config
      login_page_factors:
        - method: email_link
      jwks_url: https://api.slashid.com/.well-known/jwks.json
      jwks_refresh_interval: 15m
      jwt_expected_issuer: https://api.slashid.com
      online_tokens_validation_timeout: &quot;24h&quot;

  - id: authz_vip
    type: opa
    enabled: false
    parameters:
      &amp;lt;&amp;lt;: *slashid_config
      cookie_with_token: SID-AuthnProxy-Token
      policy_decision_path: /authz/allow
      policy: |
        package authz
        import future.keywords.if
        default allow := false
        allow if input.request.parsed_token.payload.groups[_] == &quot;vips&quot;
# ...
    - pattern: &quot;*/profile/vip&quot;
        plugins:
        authn_proxy:
            enabled: true
        authz_vip:
            enabled: true
    - pattern: &quot;*/admin&quot;
        plugins:
        authn_proxy:
            enabled: true
        authz_admin:
            enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The Edge Authenticaiton pattern&lt;/h3&gt;
&lt;p&gt;Continuing with the Kubernetes example, we can deploy Gate between the Kubernetes load balancer and the rest of the backend.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/auth-patterns/api-gateway.png&quot; alt=&quot;api gateway&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After deploying Gate as a Kubernetes service, the necessary step is to add a &lt;code&gt;LoadBalancer&lt;/code&gt; service as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  labels:
    app: gate
  name: gate
spec:
  externalTrafficPolicy: Local
  ports:
    - port: 8080
      targetPort: 8080
  selector:
    app: gate
  sessionAffinity: None
  type: LoadBalancer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here on, routes, &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins&quot;&gt;plugins&lt;/a&gt;, and the Gate configuration are defined as usual.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The architecture of your backend is a critical decision, dependent on the threat model, complexity, and heterogeneity of the deployment, and latency requirements.&lt;/p&gt;
&lt;p&gt;In this article, we&apos;ve surveyed some of the most common patterns we observe in the industry. Gate can help implement your backend identity posture without dictating a specific architecture.&lt;/p&gt;
&lt;p&gt;We’d love to hear any feedback you may have. Please contact us at &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;contact@slashid.dev&lt;/a&gt;! Try out Gate with a &lt;a href=&quot;https://console.slashid.dev/signup/gate?utm_source=authpatterns-bp&quot;&gt;free account&lt;/a&gt;.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Introducing Data Vault - Secure HSM-backed PII storage directly from the frontend</title><link>https://slashid.dev/blog/data-vault-release/</link><guid isPermaLink="true">https://slashid.dev/blog/data-vault-release/</guid><description>Today we are releasing Data Vault, which allows the safe and compliant storage of sensitive user data directly from the frontend. Data Vault takes care of data localization and protection transparently, without having to build ad-hoc infrastructure to handle encryption or key management and rotation.</description><pubDate>Mon, 07 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Building a truly secure data storage is still extremely hard, and the increased regulatory pressure makes mishandling of users’ personally identifiable information (PII) a very costly mistake.&lt;/p&gt;
&lt;p&gt;We built &lt;a href=&quot;https://www.slashid.dev/products/vault&quot;&gt;Data Vault&lt;/a&gt; to help developers store user data and PII securely, without having to worry about data localization, key management, key rotation or the million other issues you face when building and using cryptographic primitives.&lt;/p&gt;
&lt;p&gt;Data Vault is ideal for scenarios in which you need to save sensitive user data such as data collected during KYC, Health-related records and credit cards data but it can also be used as a simple and convenient key-value store for user attributes. For instance, we use Data Vault to keep track of a user&apos;s cart in our e-commerce &lt;a href=&quot;https://commdemo.slashid.com/&quot;&gt;demo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can try Data Vault yourself in our &lt;a href=&quot;https://playground.slashid.com/&quot;&gt;playground&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Data Vault has a number of properties that are not available through a database or through products like Hashicorp Vault:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Database-agnostic&lt;/li&gt;
&lt;li&gt;Customers can bring their own Hardware Security Modules (HSM)&lt;/li&gt;
&lt;li&gt;Accessible directly from the frontend as well as the backend&lt;/li&gt;
&lt;li&gt;Handles encryption and data localization transparently&lt;/li&gt;
&lt;li&gt;Hardware-based root of trust to implement crypto-anchoring and avoid mass data exfiltration&lt;/li&gt;
&lt;li&gt;Data can be either globally replicated or localized to an individual region&lt;/li&gt;
&lt;li&gt;Compliance with GDPR and similar regulations by (i) keeping an audit log of data access requests and (ii) using crypto-shredding to service data deletion requests&lt;/li&gt;
&lt;li&gt;No noticeable latency for the end user&lt;/li&gt;
&lt;li&gt;Ability to perform encrypted searches with equality&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How do you use Data Vault?&lt;/h2&gt;
&lt;p&gt;From the outside, Data Vault’s interface looks like a standard REST API, coupled with a set of SDKs (both client-side and server-side) that together allow developers to store and retrieve users and their associated data.&lt;/p&gt;
&lt;p&gt;Below you’ll see a few examples of how to use Data Vault, both client-side through our SDK and server-side through our APIs. We&apos;ll show a few examples with and without SlashID as the Identity Provider.&lt;/p&gt;
&lt;h3&gt;Data Vault with SlashID Access client-side&lt;/h3&gt;
&lt;p&gt;If you are using SlashID’s &lt;a href=&quot;https://www.slashid.dev/products/access&quot;&gt;Access module&lt;/a&gt; for identity management, then Data Vault is already provided as part of Access.&lt;/p&gt;
&lt;p&gt;For instance, this is a code snippet that shows how to register or sign-in a user and how to store and retrieve encrypted attributes using our client-side JavaScript SDK:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var sid = new slashid.SlashID({ oid: Organization_ID })
…
const user = await sid.id(
   Organization_ID,
   {
       type: &quot;phone_number&quot;,
       value: &quot;+13337777777&quot;
   },
   {
       method: &quot;webauthn&quot;
   }
)
…
await user.set({&quot;passport&quot;: &quot;E65471234&quot;})

const attrs = await user.get()
// =&amp;gt; attrs === {&quot;passport&quot;: &quot;E65471234&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example above, we are registering/logging in a user with the phone number &lt;code&gt;+13337777777&lt;/code&gt;, and using &lt;code&gt;webauthn&lt;/code&gt; as the authentication method. By default, if the user connected to the website from Lisbon their data would be automatically stored in the EU, and if they connected from New York the data would be stored in the US - you can override that choice in the &lt;code&gt;id()&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;Calling &lt;code&gt;set&lt;/code&gt; on a user creates a new attribute in our Data Vault module. When that happens an attribute-specific encryption key is generated, which is used to encrypt &lt;code&gt;passport&lt;/code&gt;. The key is itself encrypted with a user-specific key generated at user-creation time.&lt;/p&gt;
&lt;p&gt;Calling &lt;code&gt;get&lt;/code&gt; on a user allows you to retrieve one or more attributes associated with the user. The attributes are decrypted and returned in clear-text to the caller.&lt;/p&gt;
&lt;p&gt;Note how, throughout the example, you don&apos;t have to worry about encryption nor data localization, and the user data never touches your own infrastructure.&lt;/p&gt;
&lt;h3&gt;Client-side Data Vault without SlashID Access&lt;/h3&gt;
&lt;p&gt;If you are not using Access for Identity Management, you can still use Data Vault securely from your frontend.&lt;/p&gt;
&lt;p&gt;You first need to create a user in SlashID through our APIs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X POST &apos;https://api.sandbox.slashid.com/persons&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395&apos; \
-H &apos;SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks&apos; \
--data-raw &apos;[
  {
    &quot;attributes&quot;: {},
    &quot;handles&quot;: [
      {
        &quot;type&quot;: &quot;email_address&quot;,
        &quot;value&quot;: &quot;user_blogpost@user.com&quot;
      }
    ],
    &quot;region&quot;: &quot;us-iowa&quot;,
    &quot;roles&quot;: [
    ],
    &quot;groups&quot;: [
    ]
  }
]&apos;

{
    &quot;result&quot;:
    [
        {
            &quot;personID&quot;:&quot;pid:01975547e47da59d74d5fa5df2e1f1df3ffa1a2ece1785c3e97e6ede0478e89e8d3ca7612a:1&quot;,
            &quot;active&quot;:true,
            &quot;roles&quot;:[]
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you have the user you can mint an access token for it through our APIs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X POST &apos;https://api.sandbox.slashid.com/persons/pid:01975547e47da59d74d5fa5df2e1f1df3ffa1a2ece1785c3e97e6ede0478e89e8d3ca7612a:1/mint-token&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395&apos; \
-H &apos;SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks&apos;
{
 &quot;result&quot;: &quot;eyJhbGciOiJSUzI1NiIsImtpZCI6InZFcHNGZyJ9.dsaRoZW50aWNhdGVkX21ldGhvZHMiOlsiYXBpIl0sImV4cCI6MTY2NzU2NzIxMCwiZmlyc3RfdG9rZW4iOmZhbHNlLCJpYXQiOjE2Njc0ODA4MTAsImlzcyI6InNhbmRib3guc2xhc2hpZC5kZXYvYXV0aG4iLCJqdGkiOiI2MDU3Yzc1MDNhYzhkNjM2NWVkMDZkMDFkOGIwNGZlOSIsIm9pZCI6ImY5NzhhNmJkLTNlNDUtYmNkYS1jYjRlLTU3M2QwYmFkMTU1YiIsInVzZXJfaWQiOiJwaWQ6MDE5NzU1NDdlNDdkYTU5ZDc0ZDVmYTVkZjJlMWYxZGYzZmZhMWEyZWNlMTc4NWMzZTk3ZTZlZGUwNDc4ZTg5ZThkM2NhNzYxMmE6MSJ9.EMwqVfJLvVVhOy_TQ7UuX1kiXphXBwt3E38Rkdkgkld_C5TfHqi7BRgwyc6FQEsnX4QzoB9-vCjupVxpAlY_PuVaxvNI3j1NUilZuPB8GMsiUS5Ivc1k9yUBMt11qOd1GR6lU9GiUZnXzOaYiOMc5LZJs8ig7YsFJCSZ34QpgcwQAkR0gIWOBezc_q4vHel8NnOgjx4syhUz-1gLngyqgwpdWae1zmzMY9zrof7NfXnSr69waLQvToA7gQWDgC0TLimpvNNiHrE3Do7v0JgXJ7JQ63ARE7ES0uppaX-mvpo7HYp32RfDmh29EOsMkSt0NFrY8t1UhUvySh57ECkePA&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The resulting token can be loaded in Javascript through the SDK as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const user = new slashid.User(
  &apos;eyJhbGciOiJSUzI1NiIsImtpZCI6InZFcHNGZyJ9.dsaRoZW50aWNhdGVkX21ldGhvZHMiOlsiYXBpIl0sImV4cCI6MTY2NzU2NzIxMCwiZmlyc3RfdG9rZW4iOmZhbHNlLCJpYXQiOjE2Njc0ODA4MTAsImlzcyI6InNhbmRib3guc2xhc2hpZC5kZXYvYXV0aG4iLCJqdGkiOiI2MDU3Yzc1MDNhYzhkNjM2NWVkMDZkMDFkOGIwNGZlOSIsIm9pZCI6ImY5NzhhNmJkLTNlNDUtYmNkYS1jYjRlLTU3M2QwYmFkMTU1YiIsInVzZXJfaWQiOiJwaWQ6MDE5NzU1NDdlNDdkYTU5ZDc0ZDVmYTVkZjJlMWYxZGYzZmZhMWEyZWNlMTc4NWMzZTk3ZTZlZGUwNDc4ZTg5ZThkM2NhNzYxMmE6MSJ9.EMwqVfJLvVVhOy_TQ7UuX1kiXphXBwt3E38Rkdkgkld_C5TfHqi7BRgwyc6FQEsnX4QzoB9-vCjupVxpAlY_PuVaxvNI3j1NUilZuPB8GMsiUS5Ivc1k9yUBMt11qOd1GR6lU9GiUZnXzOaYiOMc5LZJs8ig7YsFJCSZ34QpgcwQAkR0gIWOBezc_q4vHel8NnOgjx4syhUz-1gLngyqgwpdWae1zmzMY9zrof7NfXnSr69waLQvToA7gQWDgC0TLimpvNNiHrE3Do7v0JgXJ7JQ63ARE7ES0uppaX-mvpo7HYp32RfDmh29EOsMkSt0NFrY8t1UhUvySh57ECkePA&apos;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From this point onwards, you can use the user object just like you would if you were using Access for Identity Management:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await user.set({ passport: &apos;E65471234&apos; })

const attrs = await user.get()
// =&amp;gt; attrs === {&quot;passport&quot;: &quot;E65471234&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Accessing Data Vault from the backend&lt;/h3&gt;
&lt;p&gt;Accessing Data Vault from your backend is straightforward through a simple REST API.
Here is an example to retrieve all the attributes in clear-text for a given user:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X GET &apos;https://api.sandbox.slashid.com/persons/pid:01975547e4f57cd8ac6a0f80f50793d5718b6056ca072101b52b060f28302882903bdd7d1d:1/attributes&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395&apos; \
-H &apos;SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks&apos;

{
  &quot;result&quot;: {
    &quot;email&quot;: &quot;testuser_32819h@slashid.dev&quot;,
    &quot;firstName&quot;: &quot;John&quot;,
    &quot;lastName&quot;: &quot;Smith&quot;,
    &quot;passport&quot;: &quot;E65471234&quot;,
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To add a new attribute we can PUT it as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X PUT &apos;https://api.sandbox.slashid.com/persons/pid:01975547e4f57cd8ac6a0f80f50793d5718b6056ca072101b52b060f28302882903bdd7d1d:1/attributes&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395&apos; \
-H &apos;SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks&apos;
--data-raw &apos;{&quot;creditcard_num&quot;: &quot;324345678123403&quot;}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And to delete an attribute, it’s a simple DELETE:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X DELETE &apos;https://api.sandbox.slashid.com/persons/pid:01975547e4f57cd8ac6a0f80f50793d5718b6056ca072101b52b060f28302882903bdd7d1d:1/attributes?attributes=lastName&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395&apos; \
-H &apos;SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X GET &apos;https://api.sandbox.slashid.com/persons/pid:01975547e4f57cd8ac6a0f80f50793d5718b6056ca072101b52b060f28302882903bdd7d1d:1/attributes&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395&apos; \
-H &apos;SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks&apos;

{
  &quot;result&quot;: {
    &quot;email&quot;: &quot;testuser_32819h@slashid.dev&quot;,
    &quot;firstName&quot;: &quot;John&quot;,
    &quot;creditcard_num&quot;: &quot;324345678123403&quot;,
    &quot;passport&quot;: &quot;E65471234&quot;,
    &quot;uuid&quot;: &quot;1909981882&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, you can selectively GET individual attributes to minimize accessing unneeded clear-text confidential information:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X GET &apos;https://api.sandbox.slashid.com/persons/pid:01975547e4f57cd8ac6a0f80f50793d5718b6056ca072101b52b060f28302882903bdd7d1d:1/attributes?attributes=creditcard_num&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: c4681245-11d2-4fa5-835f-378b04ff7395&apos; \
-H &apos;SlashID-API-Key: NnfXQCnvn/YvwHhVtaHF39/8X8kqAzks&apos;

{
  &quot;result&quot;: {
    &quot;creditcard_num&quot;: &quot;324345678123403&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The internals&lt;/h2&gt;
&lt;p&gt;We provide a deep-dive on our encryption scheme in this &lt;a href=&quot;https://www.slashid.dev/blog/app-layer-encryption&quot;&gt;blogpost&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At a high level, we decided early on that our encryption layer would be database-agnostic, because we wanted the ability to expand datastore coverage beyond our current database, if needed.&lt;/p&gt;
&lt;p&gt;Some data structures in our architecture are encrypted and globally replicated, while others are encrypted but localized to a specific region.&lt;/p&gt;
&lt;p&gt;We make extensive use of &lt;a href=&quot;https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#enveloping&quot;&gt;envelope encryption&lt;/a&gt;. Our keys are encrypted following a tree-like logic as shown schematically in the picture below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/app-layer-encryption/tree.png&quot; alt=&quot;SlashID Key Tree&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Properties of the key hierarchy&lt;/h3&gt;
&lt;p&gt;The key hierarchy mentioned above is generated per-region and this structure brings a number of advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A compromised key doesn’t compromise all the data&lt;/li&gt;
&lt;li&gt;Deleting user data can be achieved through &lt;a href=&quot;https://en.wikipedia.org/wiki/Crypto-shredding&quot;&gt;crypto-shredding&lt;/a&gt; by deleting the corresponding user key&lt;/li&gt;
&lt;li&gt;Data can be replicated globally without violating data residency requirements since the keys are localized per-region&lt;/li&gt;
&lt;li&gt;The root of the chain of trust is stored in a Hardware Security Module (HSM) making a whole-database compromise an extremely arduous task&lt;/li&gt;
&lt;li&gt;We can enforce fine-grained access control on the HSM to tightly control and reduce the number of services able to decrypt subtrees&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Key lifecycle&lt;/h3&gt;
&lt;p&gt;Keys are created on demand when a new organization, a new user or a new attribute is generated. Keys are never stored unencrypted anywhere at rest and only one service within SlashID’s infrastructure has the ability to access the HSM to decrypt the master key, significantly reducing the chances of compromise.&lt;/p&gt;
&lt;p&gt;Derived keys are stored in a database that is encrypted, geo-localized and backed-up every 24 hours.&lt;/p&gt;
&lt;p&gt;Lastly, all keys are rotated at regular, configurable intervals.&lt;/p&gt;
&lt;h3&gt;The storage engine&lt;/h3&gt;
&lt;p&gt;While Data Vault is database agnostic and is exposed as a key-value storage, we are currently using &lt;a href=&quot;https://cloud.google.com/sql&quot;&gt;Cloud SQL for PostgreSQL&lt;/a&gt; for attribute storage. Cloud SQL is deployed in multiple GCP regions to obtain as much granular coverage as possible for data localization purposes.&lt;/p&gt;
&lt;p&gt;As mentioned earlier, key material is stored in the region closest to the user to comply with data localization laws. Encrypted user data is either localized with the key or globally replicated across different regions to reduce latency, depending on access patterns.&lt;/p&gt;
&lt;h2&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;We have a number of exciting upcoming features for Data Vault:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fine-grained attributes&lt;/li&gt;
&lt;li&gt;Tokenization and PCI compliance&lt;/li&gt;
&lt;li&gt;In-browser encryption&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And many more!&lt;/p&gt;
&lt;p&gt;In the next few weeks we’ll describe how we are approaching in-browser encryption so you can decide whether to encrypt the data in the backend or let the user encrypt data client-side in a non-custodial fashion.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If you are interested in Data Vault or you have built a similar system yourself, we’d love to hear from you. Please reach out to us!&lt;/p&gt;
</content:encoded><author>Giovanni Gola, Vincenzo Iozzo</author></item><item><title>Microsoft Actor Token Forgery</title><link>https://slashid.dev/blog/actor-token-forgery-overview/</link><guid isPermaLink="true">https://slashid.dev/blog/actor-token-forgery-overview/</guid><description>Actor Token Forgery is one of the many techniques adopted by attackers to escalate privileges and move laterally via identity vector.  This post reconstructs the attack flow, maps it to MITRE ATT&amp;CK, and outlines immediate detection and defense actions.</description><pubDate>Sun, 09 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Authentication no longer hinges on passwords alone but on tokens, keys, and trust relationships between applications and service accounts. While this shift has enabled seamless integrations and powerful automation, it has also opened the door to a stealthy and dangerous class of attacks: Actor Token Forgery. This blog post explores Actor Token Forgery from an attacker’s point of view, demonstrating how malicious actors exploit legitimate trust chains to impersonate users and applications.&lt;/p&gt;
&lt;p&gt;Actor Token Forgery is one of the many techniques adopted by attackers to escalate privileges and move laterally via identity.&lt;/p&gt;
&lt;p&gt;This post reconstructs the attack flow, maps it to MITRE ATT&amp;amp;CK, and outlines immediate detection and defense actions.&lt;/p&gt;
&lt;h2&gt;What are Actor Tokens?&lt;/h2&gt;
&lt;p&gt;Actor tokens are tokens that are issued by a service called Access Control Service. These tokens allow certain Microsoft services to impersonate users/identities in an Entra directory. Exchange and Sharepoint are two of the most common services using Actor tokens.&lt;/p&gt;
&lt;p&gt;Actor tokens lack a lot of security controls and visibility, in particular:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There are no logs at issuance&lt;/li&gt;
&lt;li&gt;There are no logs of usage, as these tokens in Entra are logged as the impersonated identity&lt;/li&gt;
&lt;li&gt;They cannot be revoked&lt;/li&gt;
&lt;li&gt;Conditional Access policies don&apos;t apply to them&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;What is Actor Token Forgery?&lt;/h2&gt;
&lt;p&gt;Actor Token Forgery is a technique where attackers forge OAuth 2.0 access tokens using either a stolen certificate or an injected trusted key. These tokens are indistinguishable from legitimate ones: they’re signed with trusted credentials, issued by Microsoft’s own Security Token Service (STS), and pass all validation checks. With them, attackers can authenticate to Microsoft Graph, Exchange, SharePoint, or any federated app in the tenant, without triggering MFA, without a login event, and without raising any alarms.&lt;/p&gt;
&lt;p&gt;This technique is similar in spirit to the infamous Golden SAML attack. But while Golden SAML abuses federated identity infrastructure like ADFS, Actor Token Forgery happens entirely within Microsoft Entra (formerly Azure AD), making it harder to detect and easier to automate.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/actor-token-forgery/Picture1.png&quot; alt=&quot;Attack Flow Diagram&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The Attack Flow&lt;/h2&gt;
&lt;p&gt;Like many identity-centric attacks, Actor Token Forgery requires an initial foothold. This could come from a compromised global admin account, an exposed CI/CD token, or a misconfigured app registration with excessive permissions.&lt;/p&gt;
&lt;p&gt;Once inside, the attacker hunts for service principals or applications with high-privilege permissions such as &lt;code&gt;Directory.Read.All&lt;/code&gt;, &lt;code&gt;Mail.Read&lt;/code&gt;, or &lt;code&gt;User.Read.All&lt;/code&gt;. Using Microsoft Graph, they enumerate applications and check their &lt;code&gt;keyCredentials&lt;/code&gt; and &lt;code&gt;appRoles&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;roadtx graphrequest https://graph.microsoft.com/v1.0/servicePrincipals&lt;/li&gt;
&lt;li&gt;roadtx graphrequest https://graph.microsoft.com/v1.0/applications&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The next step is critical: the attacker either steals an existing certificate (PFX) from disk or injects a new one via Graph API. Certificate injection requires &lt;code&gt;Application.ReadWrite.All&lt;/code&gt; or equivalent permissions, which are often granted to CI/CD pipelines, automation accounts, or overly trusted apps.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/actor-token-forgery/2.jpg&quot; alt=&quot;Attack Flow Diagram&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Each parameter in the command defines a specific part of the OAuth 2.0 / OpenID Connect token request that ROADtools performs against Azure AD:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;-c 93c5e1b2-0fa0-4126-a2d2-9c58f1cd7685&lt;/code&gt; — Client (Application) ID
The globally unique identifier of the Azure AD application (service principal) that is authenticating. It tells Azure AD which app is requesting the token and links the request to that app&apos;s registration within the tenant.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-t endrit.onmicrosoft.com&lt;/code&gt; — Tenant
Specifies the Azure AD tenant that will issue the token. It&apos;s the logical directory in which the app lives and where authentication takes place. In this example, the tenant domain endrit.onmicrosoft.com corresponds to a particular tenant ID (tid) shown later in the decoded token.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-s &quot;msgraph/.default offline_access&quot;&lt;/code&gt; — Requested Scopes / Resource Permissions&lt;/li&gt;
&lt;li&gt;&lt;code&gt;offline_access&lt;/code&gt; adds the ability to obtain refresh tokens for long-lived access.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--key-pem&lt;/code&gt; — Private-Key File (Proof of Possession) – PFX&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--cert-pem&lt;/code&gt; — Public-Key / Certificate File&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This allows attackers to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Call Microsoft Graph with app-level permissions the app already has. Meaning that they can:
&lt;ul&gt;
&lt;li&gt;Add or replace keyCredentials&lt;/li&gt;
&lt;li&gt;Create new service principal&lt;/li&gt;
&lt;li&gt;Assign roles or create privileged accounts&lt;/li&gt;
&lt;li&gt;Use obtained token to call other MS APIs (SharePoint, Exchange,…)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;roadtx graphrequest -m POST &quot;https://graph.microsoft.com/v1.0/applications/:98449b3b-bfc5-4459-82cc-f4c1994df7e9addPassword&quot; -d &quot;{\&quot;passwordCredential\&quot;:{\&quot;displayName\&quot;:\&quot;Backdoor App\&quot;}}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Diving deeper&lt;/h3&gt;
&lt;p&gt;This figure illustrates a full Actor Token Forgery attack chain executed using ROADtools, a post-exploitation toolkit. The first command shows the generation of an actor token (getactortoken) using a compromised client certificate linked to an app registration (client_id: &lt;code&gt;93c5e1b2-0fa0-4126-a2d2-9c58f1cd7685&lt;/code&gt;) within the Entra tenant. This token impersonates an identity (&lt;code&gt;00000002-0000-0ff1-ce00-000000000000@outlook.office.com&lt;/code&gt;) typically used by Microsoft services such as Outlook or Graph API, granting delegated access under this identity. After writing the token to .roadtools_actortoken, the describe command decodes the JWT payload, revealing fields like appid, identityprovider, oid, and tid, confirming it is a signed token for Microsoft Graph with aud and iss aligned to the compromised tenant.&lt;/p&gt;
&lt;p&gt;In the next step, getimpersonationtoken is used to forge a composite token impersonating a target user (&lt;code&gt;alice@endrit.onmicrosoft.com&lt;/code&gt;) using the previously obtained actor token. The target resource is SharePoint (endrit.sharepoint.com), and the resulting impersonation token is stored in .roadtools_auth. The final describe output reveals the forged token contains an &quot;actortoken&quot; claim, which embeds the original actor token inside the composite, thereby linking the forged token to the service principal that signed it. This type of attack effectively allows lateral movement, access escalation, and cross-service impersonation without MFA or password interaction, bypassing traditional logon detection tools, and representing a serious threat if certificate theft or app registration abuse occurs.&lt;/p&gt;
&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sharepoint accepted a self-signed, unsigned token (&quot;alg&quot;: &quot;none&quot;), because it came through a trusted hybrid path.&lt;/li&gt;
&lt;li&gt;The “iss” (issuer) is the same one as for the legitimate actor token, and the same for the forged copy after the ROADtools_actor token command.&lt;/li&gt;
&lt;li&gt;The difference is origin: one came from Microsoft’s STS, the other was self-signed by the attacker, yet still trusted by Exchange Online because it’s signed with the valid hybrid certificate’s key.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/blog/actor-token-forgery/3.jpg&quot; alt=&quot;Attack Flow Diagram&quot; /&gt;
&lt;img src=&quot;/blog/actor-token-forgery/4.jpg&quot; alt=&quot;Attack Flow Diagram&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Assertion key and S2S Authentication Flaws&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/blog/actor-token-forgery/Picture3.png&quot; alt=&quot;Attack Flow Diagram&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When the on-prem Exchange server requests an actor token from Microsoft’s Access Control Service, that token is an assertion — a signed JWT stating “Exchange on-prem (actor) is acting on behalf of user X”.&lt;/p&gt;
&lt;p&gt;Exchange Online doesn’t talk directly to Entra ID again; instead, it takes that assertion and embeds it verbatim inside a new token — the unsigned bearer token you see being sent to Exchange Online. Because Exchange Online already trusts the on-prem service principal and its certificate (configured as “trusted for delegation”), it doesn’t need to re-validate the signature against Entra ID. It simply unwraps the actor token, verifies the issuer (Exchange Hybrid), and accepts the claims (user identity, roles, scopes).&lt;/p&gt;
&lt;p&gt;Thus, the “actor token assertion” and the “unsigned bearer token” contain the same inner data: one is issued by ACS and signed, and the other is just that same blob re-used and forwarded by Exchange Online to prove delegated identity.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Service-to-Service (S2S) authentication model was built for seamless communication between trusted Microsoft workloads like Exchange and SharePoint but creates a major blind spot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When a trusted service principal (e.g., an on-prem Exchange hybrid server) requests an actor token from Microsoft’s Access Control Service (ACS), it receives a signed JWT. Exchange Online then embeds that token inside an unsigned bearer token (&quot;alg&quot;: &quot;none&quot;) to skip revalidation with Entra ID, meaning no logs, no Conditional Access, and no revocation. These S2S tokens last up to 24 hours, are non‑revocable, and invisible to standard identity monitoring. Because the bearer token simply reuses the actor token’s contents, compromising a certificate from a TrustedForDelegation service lets attackers impersonate any tenant user, including admins, and access Exchange, SharePoint, and OneDrive entirely outside Entra ID’s visibility.&lt;/p&gt;
&lt;h2&gt;Hardening Against Actor Token Forgery &amp;amp; S2S Assertion Abuse&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audit certificate material in service principals:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;a. Query:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ServicePrincipal
| where preferredTokenSigningKeyThumbprint != &quot;&quot;
    or array_length(keyCredentials) &amp;gt; 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;b. Flag:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;keyCredentials.endDateTime &amp;gt; now() + 365d&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;trustedForDelegation == true&lt;/code&gt;
c. Action:&lt;/li&gt;
&lt;li&gt;Rotate all credentials ≥ 90 days old&lt;/li&gt;
&lt;li&gt;Enforce short-lived certs via Graph automation:&lt;pre&gt;&lt;code&gt;az ad app credential list --id &amp;lt;appid&amp;gt;
az ad app credential reset --id &amp;lt;appid&amp;gt; --append --years 0.25
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Monitor certificate insertions via Graph API:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;a. Telemetry:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AuditLogs | where ActivityDisplayName == &quot;Add keyCredentials&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;b. Detection logic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New certificate on high-privilege app (Graph, Exchange, EWS) without matching &lt;code&gt;appRoleAssignment&lt;/code&gt; change&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flag or block tokens with &lt;code&gt;&quot;alg&quot;: &quot;none&quot;&lt;/code&gt; (unsigned JWTs)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Correlate mailbox access patterns with Entra logs:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rationale:&lt;/strong&gt; S2S / actor tokens are accepted by Exchange/SharePoint but &lt;strong&gt;never logged in Entra ID&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;S2S tokens leave &lt;strong&gt;no Entra ID log footprint&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Look for unusual file access scopes&lt;/strong&gt; in Graph calls (e.g., &lt;code&gt;Files.Read.All&lt;/code&gt; by apps)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Review conditional access bypass paths:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Important:&lt;/strong&gt; S2S flows do &lt;strong&gt;not&lt;/strong&gt; trigger Conditional Access&lt;/li&gt;
&lt;li&gt;Mitigation:
&lt;ul&gt;
&lt;li&gt;Restrict usage of client-credential flow apps:&lt;pre&gt;&lt;code&gt;az ad app update --id &amp;lt;appid&amp;gt; --set oauth2AllowImplicitFlow=false
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Integrity-based detection of forged assertions&lt;/h3&gt;
&lt;p&gt;In this example, the actor token&apos;s header exposes the thumbprint:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JWT token thumbprint:&lt;/strong&gt; &lt;code&gt;x5t/kid = yEUwmXWL107Cc-7QZ2WSbeOb3sQ&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the legitimate Entra service principal lists a registered certificate with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Registered certificate:&lt;/strong&gt; &lt;code&gt;customKeyIdentifier = 17A0A7B70A586359E83BCB9BDAA3D3402F29D9DF&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Key finding:&lt;/strong&gt; Because the token thumbprint does &lt;strong&gt;not&lt;/strong&gt; appear in the service principal&apos;s &lt;code&gt;keyCredentials&lt;/code&gt; set, the token&apos;s signing certificate is &lt;strong&gt;unregistered in Entra&lt;/strong&gt;—proving it was generated offline with a &lt;strong&gt;forged or stolen PFX&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/actor-token-forgery/6.png&quot; alt=&quot;Attack Flow Diagram&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Attacker&apos;s Toolbox&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Exported PFX (cert+key) from Entra or Exchange Hybrid&lt;/li&gt;
&lt;li&gt;ROADtools + ROADtx modules (getactortoken, getimpersonationtoken,…)&lt;/li&gt;
&lt;li&gt;Custom JWT forging tools&lt;/li&gt;
&lt;li&gt;Known AppIDs / Service Principals
&lt;ul&gt;
&lt;li&gt;Exchange: &lt;code&gt;00000002-0000-0ff1-ce00-000000000000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Graph: &lt;code&gt;00000003-0000-0000-c000-000000000000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;SharePoint: &lt;code&gt;00000003-0000-0ff1-ce00-000000000000&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Graph API post-exploitation
&lt;ul&gt;
&lt;li&gt;GET &lt;code&gt;/users/{id}/drive/root/children&lt;/code&gt; (shown below &amp;amp; demo), GET /users, GET /users/{id}&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/blog/actor-token-forgery/screenshot2.jpg&quot; alt=&quot;Attack Flow Diagram&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This screenshot shows a successful Microsoft Graph call made after ROADtools generated an actor/impersonation token.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;API endpoint:&lt;/strong&gt; &lt;code&gt;GET /users/{id}/drive/root/children&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Attack flow:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Attacker produces a &lt;strong&gt;signed actor token&lt;/strong&gt; (PFX-based)&lt;/li&gt;
&lt;li&gt;Uses it to request a &lt;strong&gt;delegated token&lt;/strong&gt; that can read OneDrive contents&lt;/li&gt;
&lt;li&gt;Makes Graph API call with forged credentials&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Technical breakdown:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Attack type:&lt;/strong&gt; Actor token forgery / abuse of certificate-backed app credentials&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication proof:&lt;/strong&gt; Token proves possession of a private key&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Accepted by Entra/Exchange/Graph as a valid service-issued assertion&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result:&lt;/strong&gt; Graph API returns the victim&apos;s drive items &lt;strong&gt;without any interactive login or MFA&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Key security implications:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;❌ No MFA challenge&lt;/li&gt;
&lt;li&gt;❌ No user interaction required&lt;/li&gt;
&lt;li&gt;❌ Bypasses Conditional Access policies&lt;/li&gt;
&lt;li&gt;✅ Appears as legitimate service-to-service authentication&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;video width=&quot;100%&quot; controls&gt;
  &lt;source src=&quot;/blog/actor-token-forgery/Actor_token_demo.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
  Your browser does not support the video tag.
&lt;/video&gt;&lt;/p&gt;
&lt;h2&gt;What SlashID detects&lt;/h2&gt;
&lt;p&gt;SlashID is built to detect and respond to attacks such as Actor Token Forgery. In particular with SlashID you can detect:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service accounts with dangerous OAuth 2 scopes&lt;/li&gt;
&lt;li&gt;Anomalous &lt;code&gt;keyCredential&lt;/code&gt; changes&lt;/li&gt;
&lt;li&gt;Anomalous &lt;code&gt;appRoleAssignment&lt;/code&gt; changes&lt;/li&gt;
&lt;li&gt;Anomalous service accounts behavior&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Actor Token Forgery is a high-impact identity attack that leverages trust relationships and certificate-based signing to bypass normal identity controls. Because the attack abuses legitimate service principals and S2S (service-to-service) flows, it can fail to generate any meaningful records in Entra ID or Conditional Access telemetry—creating a dangerous blind spot for defenders. In practice, an attacker who can obtain or inject a signing certificate into a trusted application or hybrid service principal can impersonate any user in the tenant and access Exchange, SharePoint, OneDrive, and Graph APIs without interactive sign-ins or MFA challenges.&lt;/p&gt;
&lt;p&gt;Defenders can significantly reduce this risk by treating certificate material and service principals as first-class security objects: rotate credentials frequently, enforce short certificate lifetimes, monitor and alert on every &lt;code&gt;keyCredentials&lt;/code&gt; insertion or modification, and correlate app-level activity with Entra ID and mailbox access logs. Where possible, minimize the number of applications trusted for delegation and restrict app permissions to least privilege. Finally, make integrity checks—like validating token thumbprints against registered keyCredentials—part of routine identity hygiene and automated threat-detection playbooks.&lt;/p&gt;
&lt;p&gt;If you maintain hybrid Exchange or any S2S integrations, prioritize the following immediate actions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Inventory service principals and their certificates&lt;/li&gt;
&lt;li&gt;Audit recent Add &lt;code&gt;keyCredentials&lt;/code&gt; events and newly created high-privilege apps&lt;/li&gt;
&lt;li&gt;Implement detection rules that correlate unsigned or unexpected tokens with certificate insertions and privileged Graph activity.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These steps will close the fastest, most obvious attack paths and give your security team the visibility needed to detect and respond to Actor Token Forgery before it becomes a full tenant compromise.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SlashID is built to detect and respond to attacks such as Actor Token Forgery. Get in touch to see how we can help.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><author>SlashID Team, Vincenzo Iozzo</author></item><item><title>Adding custom claims to identity tokens</title><link>https://slashid.dev/blog/custom-claims/</link><guid isPermaLink="true">https://slashid.dev/blog/custom-claims/</guid><description>Adding custom claims to JWTs allows you to share identity information without repeated queries to external data sources. Read on to learn how to customize claims with SlashID&apos;s webhooks.</description><pubDate>Wed, 06 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Identity token claims contain information about the subject, such as email addresses, roles, risk scores, and more, specified as key-value pairs. JWTs support two different types of claim fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Standard&lt;/strong&gt;: these claim names are registered and are commonly included in tokens as industry standards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom&lt;/strong&gt;: you can specify additional information to be added to your tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&apos;s easy to add custom claims to SlashID-issued tokens. For example, you can add meaningful details about the user who is logging in into your web app, such as their name, physical address, or roles.&lt;/p&gt;
&lt;p&gt;SlashID provides three ways to add custom claims:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using synchronous webhooks during registration/authentication.&lt;/li&gt;
&lt;li&gt;Using &lt;a href=&quot;https://developer.slashid.dev/docs/gate/use-cases/token-management/token-enrichment&quot;&gt;Gate&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Using our &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-persons-person-id-mint-token&quot;&gt;token minting API&lt;/a&gt; from your backend.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Read on to see an example of token enrichment using webhooks. If you are interested in our other approaches, take a look at the &lt;a href=&quot;https://developer.slashid.dev/docs/gate/use-cases/token-management/token-enrichment&quot;&gt;Gate documentation&lt;/a&gt; or our &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-persons-person-id-mint-token&quot;&gt;token minting API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We believe our approach has several advantages compared to others, in particular:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Testability&lt;/strong&gt;: it is significantly easier to test custom authentication logic via webhooks compared to hosted scripts or other approaches;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data protection&lt;/strong&gt;: by using a webhook you never expose key material (e.g., credentials to access a database with the enrichment information) or sensitive data to SlashID.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;: you can customize the token at any step of its journey, including at your backend level when you need to differentially enrich tokens for specific microservices.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;You can find the code for this example in our GitHub &lt;a href=&quot;https://github.com/slashid/webhooks-examples/tree/main/token-minted-webhook-example&quot;&gt;repo&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Custom claims with webhooks&lt;/h2&gt;
&lt;p&gt;SlashID supports both synchronous and asynchronous webhooks to hook all interactions between a user and SlashID, such as authentication attempts, registration, changes in attributes, and more.&lt;/p&gt;
&lt;p&gt;You can create and manage webhooks using a simple REST API - see the full documentation &lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/webhooks&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Webhooks can be attached to multiple triggers, which are either asynchronous (events) or synchronous (sync hooks). To customize token claims, we will need to use the &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/sync_hooks&quot;&gt;&lt;code&gt;token_minted&lt;/code&gt;&lt;/a&gt; sync hook. This hook is called for every authentication/registration attempt before a token is issued.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The webhook registered with the &lt;code&gt;token_minted&lt;/code&gt; trigger will receive the claims of the token that SlashID is about to mint for a user. The webhook&apos;s response can block the issuance, enrich it with custom claims, or do nothing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A typical token received by the webhook looks as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;aud&quot;: &quot;e2c8a069-7e89-cc7e-b161-3f02681c6804&quot;,
  &quot;exp&quot;: 1697652845,
  &quot;iat&quot;: 1697652545,
  &quot;iss&quot;: &quot;https://api.slashid.com&quot;,
  &quot;jti&quot;: &quot;2479ec0e-aad5-47ca-a12b-214ddaf7fc85&quot;,
  &quot;sub&quot;: &quot;065301f4-1c74-7636-ad00-da1923dc00f9&quot;,
  &quot;target_url&quot;: &quot;https://7b9c-206-71-251-221.ngrok.io/pre-auth-user-check&quot;,
  &quot;trigger_content&quot;: {
    &quot;aud&quot;: &quot;e2c8a069-7e89-cc7e-b161-3f02681c6804&quot;,
    &quot;authentications&quot;: [
      {
        &quot;handle&quot;: {
          &quot;type&quot;: &quot;email_address&quot;,
          &quot;value&quot;: &quot;vincenzo@slashid.dev&quot;
        },
        &quot;method&quot;: &quot;oidc&quot;,
        &quot;timestamp&quot;: &quot;2023-10-18T18:08:57.9002598Z&quot;
      }
    ],
    &quot;first_token&quot;: true,
    &quot;groups_claim_name&quot;: &quot;groups&quot;,
    &quot;iss&quot;: &quot;https://api.slashid.com&quot;,
    &quot;region&quot;: &quot;europe-belgium&quot;,
    &quot;request_metadata&quot;: {
      &quot;client_ip_address&quot;: &quot;206.71.251.221&quot;,
      &quot;origin&quot;: &quot;http://localhost:8080&quot;,
      &quot;user_agent&quot;: &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36&quot;
    },
    &quot;sub&quot;: &quot;065158a6-33f2-725e-a208-9d773600513a&quot;
  },
  &quot;trigger_name&quot;: &quot;token_minted&quot;,
  &quot;trigger_type&quot;: &quot;sync_hook&quot;,
  &quot;webhook_id&quot;: &quot;065301dd-b26d-7d7e-b500-6a7f180b2cdb&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using Python&apos;s FastAPI framework, we will add three custom claims to a token: a user name, a fictional department, and a flag stating whether the user is in the office based on their IP address.&lt;/p&gt;
&lt;h2&gt;1. Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Python 3.6+&lt;/li&gt;
&lt;li&gt;ngrok: https://ngrok.com/download&lt;/li&gt;
&lt;li&gt;A SlashID account. Obtain the SlashID &lt;code&gt;ORG_ID&lt;/code&gt; and &lt;code&gt;API_KEY&lt;/code&gt; from the Settings page as shown below. Register &lt;a href=&quot;https://console.slashid.dev/signup&quot;&gt;here&lt;/a&gt; for a free account.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/blog/custom-claims/org-id.png&quot; alt=&quot;Org ID&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/custom-claims/api-key.png&quot; alt=&quot;API Key&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;2. Serve the webhook locally&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ngrok http 8001: create local tunnel; please take note of the ngrok-provided URL in ngrok&apos;s output, you&apos;ll need to set it as the webhook&apos;s base &lt;code&gt;target_url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ORG_ID=[/id Org ID] API_KEY=[/id API key] uvicorn webhook:app --port 8001 --reload&lt;/code&gt;: serve the webhook locally&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: Make sure that the port is not used by any other service&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;3. Define and register the webhook&lt;/h2&gt;
&lt;p&gt;Let&apos;s first register the webhook and attach it to the &lt;code&gt;token_minted&lt;/code&gt; event.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/organizations/webhooks&apos; \
--header &apos;SlashID-OrgID: &amp;lt;ORGANIZATION ID&amp;gt;&apos; \
--header &apos;SlashID-API-Key: &amp;lt;API KEY&amp;gt;&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;target_url&quot;: &quot;https://ngrok-url/user-auth-hook&quot;,
    &quot;name&quot;: &quot;user enrichment&quot;,
    &quot;description&quot;: &quot;Webhook to receive notifications when a user attempts login or registration&quot;
}&apos;

{
    &quot;result&quot;: {

        &quot;description&quot;: &quot;Webhook to receive notifications when a user attempts login or registration&quot;,
        &quot;id&quot;: &quot;16475b49-2b12-78d7-9012-cfe0e174dcd3&quot;,
        &quot;name&quot;: &quot;user enrichment&quot;,
        &quot;target_url&quot;: &quot;https://ngrok-url/user-auth-hook&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/organizations/webhooks/16475b49-2b12-78d7-9012-cfe0e174dcd3/triggers&apos; \
--header &apos;SlashID-OrgID: &amp;lt;ORGANIZATION ID&amp;gt;&apos; \
--header &apos;SlashID-API-Key: &amp;lt;API KEY&amp;gt;&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;trigger_type&quot;: &quot;sync_hook&quot;,
    &quot;trigger_name&quot;: &quot;token_minted&quot;
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that the webhook is registered, let&apos;s take a look at a toy example for &lt;code&gt;https://api.example.com/slashid-webhooks/user-auth-hook&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
from fastapi import FastAPI, Depends, HTTPException, Request, status
from fastapi.responses import JSONResponse
import jwt
import requests
from datetime import datetime
import json

def verify_extract_token(request_body):
    jwks_client = jwt.PyJWKClient(&quot;https://api.slashid.com/organizations/webhooks/verification-jwks&quot;, headers={&quot;SlashID-OrgID&quot;: &quot;&amp;lt;ORGANIZATION ID&amp;gt;&quot;})
    webhookURL = &quot;&quot;

    try:
        header = jwt.get_unverified_header(request_body)
        key = jwks_client.get_signing_key(header[&quot;kid&quot;]).key
        token = jwt.decode(request_body, key, audience=&quot;&amp;lt;SLASHID_ORGANIZATION ID&amp;gt;&quot;, algorithms=[&quot;ES256&quot;])

        if token[&apos;target_url&apos;] != webhookURL:
            raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=f&quot;Token {token} is invalid: {e}&quot;,
        )
    except Exception as e:
        raise HTTPException(
          status_code=status.HTTP_401_UNAUTHORIZED,
          detail=f&quot;Token {token} is invalid: {e}&quot;,
      )
    return token

app = FastAPI()

@app.post(&quot;/user-auth-hook&quot;)
def hook_function(request: Request):

    request_body = await request.body()

    # Now the request has been validated
    token = verify_extract_token(request_body)

    print(json.dumps(token, indent=4))

    #extract the email address of the user
    handle = token[&apos;trigger_content&apos;][&apos;authentications&apos;][0][&apos;handle&apos;][&apos;value&apos;]
    ip_address = token[&apos;trigger_content&apos;][&apos;request_metadata&apos;][&apos;client_ip_address&apos;]

    print(f&quot;User handle {handle} and IP address {ip_address}\n&quot;)

    if handle == &quot;alex.singh@acme.com&quot;:
        in_office = False
        if ip_address == &quot;10.20.30.40&quot;:
            in_office = True

        return JSONResponse(status_code=status.HTTP_200_OK, content={
            &quot;department&quot;: &quot;R&amp;amp;D&quot;,
            &quot;name&quot;: &quot;Alex Singh&quot;,
            &quot;in_office&quot;: in_office
        })
    else:
        return JSONResponse(status_code=status.HTTP_200_OK, content={})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. Test it on a real application&lt;/h2&gt;
&lt;p&gt;You can use one of the SlashID &lt;a href=&quot;https://ecommerce-playground.slashid.dev/login?redirectTo=%2F&quot;&gt;sample applications&lt;/a&gt; to test this live.&lt;/p&gt;
&lt;p&gt;If everything works as expected, you&apos;ll see the customized token with your claims in the context bar on the left once the user logs in.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this guide, we&apos;ve shown you how to add custom claims to your tokens via synchronous webhooks.
We’d love to hear any feedback you may have: please reach out to us at &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;contact@slashid.dev&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Try out SlashID with a &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=customclaims-bp&quot;&gt;free account&lt;/a&gt;.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Introducing Anonymous Users: Balancing First-Party Data Collection and User Experience</title><link>https://slashid.dev/blog/anonymous-users/</link><guid isPermaLink="true">https://slashid.dev/blog/anonymous-users/</guid><description>With the deprecation of third-party cookies, first-party data has become crucial for websites to personalize user experiences.  SlashID introduces Anonymous Users, a feature that allows websites to collect user data without forcing users to register or log in, striking the perfect balance between data collection and user experience.</description><pubDate>Wed, 24 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The Challenge: Balancing Data Collection and User Experience&lt;/h2&gt;
&lt;p&gt;Third-party cookies have been a double-edged sword for websites. While they enabled data collection for personalized experiences, they also contributed to the controversial data brokerage industry. As third-party cookies are set to be deprecated in 2024, websites are seeking alternatives to gather first-party data while maintaining a smooth user experience.&lt;/p&gt;
&lt;p&gt;The common approach is to add a login form and incentivize users to create accounts. However, this can lead to increased friction and user drop-off. SlashID offers a better solution: &lt;strong&gt;Anonymous Users&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;With anonymous users, you can collect user information as if users had accounts and defer the account creation process to much later in their lifetime. Imagine an e-commerce website, a happy user shops there 3-4 times over several months. During that time, their anonymous user object can store information about their preferences, addresses, and more.&lt;/p&gt;
&lt;p&gt;You can defer the account creation process to any point in the future without sacrificing the ability to learn more about them so that the user can benefit from your product and won&apos;t drop off too early.&lt;/p&gt;
&lt;h2&gt;How do anonymous users work&lt;/h2&gt;
&lt;p&gt;Anonymous Users allow websites to collect user information without requiring users to create an account immediately. This feature enables a seamless user experience while still gathering valuable first-party data.&lt;/p&gt;
&lt;p&gt;At a high level, anonymous users work as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;When a visitor starts a session, SlashID creates a user with a long-lived token, emitting an &lt;strong&gt;AnonymousPersonCreated&lt;/strong&gt; event. The user is fully-fledged, allowing the addition of attributes, consent storage, and other SlashID functionalities.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When the user first authenticates or registers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For registration, SlashID attaches a handle (and optionally a credential) to the current anonymous user, emits a &lt;strong&gt;PersonCreated&lt;/strong&gt; event, and swaps the anonymous token for the new token.&lt;/li&gt;
&lt;li&gt;For authentication, SlashID discards the anonymous user and replaces it with the authenticated user.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the user doesn&apos;t create an account within the expiration period, SlashID emits an &lt;strong&gt;AnonymousPersonDeleted&lt;/strong&gt; event and wipes the user.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Implementing Anonymous Users with SlashID&apos;s React SDK&lt;/h2&gt;
&lt;p&gt;Here&apos;s a quick guide on implementing Anonymous Users using SlashID&apos;s React SDK:&lt;/p&gt;
&lt;h2&gt;1. Create an anonymous user&lt;/h2&gt;
&lt;p&gt;Enable Anonymous Users via the &lt;code&gt;&amp;lt;SlashIDProvider&amp;gt;&lt;/code&gt; component:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;SlashIDProvider oid=&quot;ORGANIZATION_ID&quot; anonymousUsersEnabled&amp;gt;
  &amp;lt;App /&amp;gt;
&amp;lt;/SlashIDProvider&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As soon as the SDK is loaded an &lt;code&gt;AnonymousUser&lt;/code&gt; instance will be available via the &lt;code&gt;useSlash()&lt;/code&gt; hook.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useSlashID, SlashIDLoaded } from &apos;@slashid/slashid&apos;

function App() {
  const { anonymousUser } = useSlashID()

  return &amp;lt;SlashIDLoaded&amp;gt;{anonymousUser.ID}&amp;lt;/SlashIDLoaded&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Persist Anonymous Users across sessions by enabling token storage&lt;/h2&gt;
&lt;p&gt;We&apos;ve made it possible to persist an anonymous user between sessions and load that user instead of creating a new one. This is particularly useful for fingerprinting users who never sign-up, or implementing a sign-up conversion strategy that spans across multiple sessions.&lt;/p&gt;
&lt;p&gt;See our full &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/guides/react-sdk-guide-anonymous-users&quot;&gt;guide&lt;/a&gt; for all the ways to load existing anonymous users.&lt;/p&gt;
&lt;p&gt;We are going to show an example using token storage. If you&apos;ve enabled token storage, anonymous users will be automatically loaded from storage when the SDK is loaded.&lt;/p&gt;
&lt;p&gt;Remember to enable token storage via &lt;code&gt;&amp;lt;SlashIDProvider&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;SlashIDProvider
  oid=&quot;ORGANIZATION_ID&quot;
  anonymousUsersEnabled
  tokenStorage=&quot;localStorage&quot;
  // or
  tokenStorage=&quot;cookie&quot;
&amp;gt;
  ...
&amp;lt;/SlashIDProvider&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. Data collection&lt;/h2&gt;
&lt;p&gt;Reading &amp;amp; writing data via user attributes works the same as it does for regular users.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { anonymousUser } = useSlashID()
if (!anonymousUser) return

const bucket = await anonymousUser.getBucket(&apos;end_user_read_write&apos;)
await bucket.set(&apos;example&apos;, { foo: &apos;bar&apos; })
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. Sign-in &amp;amp; sign-up&lt;/h2&gt;
&lt;p&gt;Sign-in &amp;amp; sign-up works the same as if the user is logged out, and anonymous users were disabled.&lt;/p&gt;
&lt;p&gt;You can have the user authenticate using the &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; component, with no additional anonymous user related config required.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Form } from &apos;@slashid/slashid&apos;

function App() {
  return &amp;lt;Form /&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or programmatically:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Form } from &apos;@slashid/slashid&apos;
import { Factor, Handle } from &apos;@slashid/slashid&apos;

function App () {
  const { logIn } = useSlashID()
  const handle: Handle = { /* your handle config */ }
  const factor: Factor = { /* your factor config */ }

  return (
    &amp;lt;Button onClick={() =&amp;gt; logIn({ handle, factor })}&amp;gt;
      Log in
    &amp;lt;/Button&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Anonymous Users provide a powerful solution for websites to gather first-party data while maintaining a frictionless user experience. By deferring account creation, websites can learn about their users without sacrificing engagement. If you&apos;re interested in implementing Anonymous Users for your business, SlashID is here to help.&lt;/p&gt;
&lt;p&gt;Don&apos;t hesitate to &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;reach out to us&lt;/a&gt; or sign up for free &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=anon-users&quot;&gt;here&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Context-aware authentication: fight identity fraud and qualify your users</title><link>https://slashid.dev/blog/context-aware-auth/</link><guid isPermaLink="true">https://slashid.dev/blog/context-aware-auth/</guid><description>Knowing your users is becoming increasingly important. Whether you&apos;re a B2B PLG business trying to convert leads or a fintech business fending off attacks, it&apos;s essential to have more context about who is accessing your platform and to customize your behavior accordingly.  In this article, we show how you can leverage SlashID&apos;s webhooks to enrich the authentication context, customize the user journey, block malicious users.</description><pubDate>Tue, 10 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Knowing your users is becoming increasingly important today both to increase revenue and to fend off attacks.&lt;/p&gt;
&lt;p&gt;Just last week 23andMe was &lt;a href=&quot;https://www.wired.com/story/23andme-credential-stuffing-data-stolen/&quot;&gt;breached&lt;/a&gt; through a credential stuffing attack. While there are multiple ways to defend against this kind of attacks, one of the key tools to use is better intelligence on bots and potential malicious traffic coming to your website.&lt;/p&gt;
&lt;p&gt;At the same time, Product Led Growth (PLG) is an increasingly prevalent paradigm in today&apos;s market. In PLG, &lt;code&gt;john.smith@gmail.com&lt;/code&gt; testing out your dashboard could be the VP at a large company that can sign off on a 6-figure deal for your company.&lt;/p&gt;
&lt;p&gt;In this article, we show how we can leverage SlashID&apos;s webhooks to enrich the authentication context and customize the user journey and block malicious users.&lt;/p&gt;
&lt;p&gt;Specifically, we&apos;ll demonstrate how to store SlashID JWTs, enrich them with risk scoring and attribution information, and block potentially malicious connections originating from Tor.&lt;/p&gt;
&lt;h2&gt;Hooking the authentication journey&lt;/h2&gt;
&lt;p&gt;SlashID supports both synchronous and asynchronous webhooks to hook all interactions between a user and SlashID (for example: authentication attempts, registration, changes in attributes and more).&lt;/p&gt;
&lt;p&gt;SlashID events are defined using protobuf. These &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/events/event_definitions&quot;&gt;definitions&lt;/a&gt; can be used to generate code for unmarshalling and handling SlashID events (for example, as received in webhook requests).&lt;/p&gt;
&lt;p&gt;You can create and manage webhooks using a simple REST API - see the full documentation &lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/webhooks&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To attach a webhook to an event, we need to make two REST calls:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Register a webhook&lt;/li&gt;
&lt;li&gt;Attach the webhook to an event&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/organizations/webhooks&apos; \
--header &apos;SlashID-OrgID: &amp;lt;ORGANIZATION ID&amp;gt;&apos; \
--header &apos;SlashID-API-Key: &amp;lt;API KEY&amp;gt;&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;target_url&quot;: &quot;https://api.example.com/slashid-webhooks/user-created&quot;,
    &quot;name&quot;: &quot;user created&quot;,
    &quot;description&quot;: &quot;Webhook to receive notifications when a new user is created&quot;
}&apos;

{
    &quot;result&quot;: {

        &quot;description&quot;: &quot;Webhook to receive notifications when a new user is created&quot;,
        &quot;id&quot;: &quot;16475b49-2b12-78d7-9012-cfe0e174dcd3&quot;,
        &quot;name&quot;: &quot;user created&quot;,
        &quot;target_url&quot;: &quot;https://api.example.com/slashid-webhooks/user-created&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/organizations/webhooks/16475b49-2b12-78d7-9012-cfe0e174dcd3/triggers&apos; \
--header &apos;SlashID-OrgID: &amp;lt;ORGANIZATION ID&amp;gt;&apos; \
--header &apos;SlashID-API-Key: &amp;lt;API KEY&amp;gt;&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;trigger_type&quot;: &quot;event&quot;,
    &quot;trigger_name&quot;: &quot;PersonCreated_v1&quot;
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have a webhook, we can test whether the hook is triggered through the &lt;code&gt;test-events&lt;/code&gt; endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/test-events&apos; \
--header &apos;SlashID-OrgID: &amp;lt;ORGANIZATION ID&amp;gt;&apos; \
--header &apos;SlashID-API-Key: &amp;lt;API KEY&amp;gt;&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;[
    {
        &quot;event_name&quot;: &quot;PersonCreated_v1&quot;
    }
]&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the webhook was registered correctly we&apos;ll receive a call from SlashID.&lt;/p&gt;
&lt;h3&gt;Validating requests&lt;/h3&gt;
&lt;p&gt;When handling a request to a webhook, it is essential that you verify the contents of the request before processing it further. With SlashID, we have made this simple by using the JSON Web Token (JWT) and JSON Web Key (JWK) standards. The body of the request to the webhook is a signed and encoded JWT (just like our authentication tokens). In order to verify it, you should first retrieve the verification key JSON Web Key Set (JWKS) for your organization using the &lt;a href=&quot;https://developer.slashid.dev/docs/api/get-organizations-webhooks-verification-jwks&quot;&gt;API&lt;/a&gt;. (Note that this endpoint is rate-limited, so we recommend caching the verification key.) You can then use this key to verify the JWT signature, and decode the body.&lt;/p&gt;
&lt;p&gt;Here&apos;s an example on how to validate a webhook request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
    jwks_client = jwt.PyJWKClient(&quot;https://api.slashid.com/organizations/webhooks/verification-jwks&quot;, headers={&quot;SlashID-OrgID&quot;: &quot;&amp;lt;ORGANIZATION ID&amp;gt;&quot;})

    request_data = request.get_data()
    try:
        header = jwt.get_unverified_header(request_data)
        key = jwks_client.get_signing_key(header[&quot;kid&quot;]).key
        token = jwt.decode(request_data, key, audience=&quot;&amp;lt;ORGANIZATION ID&amp;gt;&quot;, algorithms=[&quot;ES256&quot;])
    except Exception as e:
        raise HTTPException(
          status_code=status.HTTP_401_UNAUTHORIZED,
          detail=f&quot;Token {token} is invalid: {e}&quot;,
      )
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Blocking requests coming from Tor IPs&lt;/h2&gt;
&lt;p&gt;Malicious actors often use VPNs and Tor to hide their tracks. Generally, traffic coming through a Tor exit node should receive a higher level of scrutiny - whether
by blocking traffic or enforcing further authentication verification such as stronger authentication factor or MFA.&lt;/p&gt;
&lt;p&gt;To block authentication attempts coming from Tor IP addresses, we can do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Register a synchronous webhook on &lt;code&gt;token_minted&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Check if the IP comes from Tor. You can use multiple services for this; in our example, we&apos;ll use &lt;a href=&quot;https://seon.io/&quot;&gt;seon.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Block a request if it comes from Tor&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We&apos;ll use the same webhook as in the example above, but now we need to attach it to the &lt;code&gt;token_minted&lt;/code&gt; synchronous hook:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/organizations/webhooks/16475b49-2b12-78d7-9012-cfe0e174dcd3/triggers&apos; \
--header &apos;SlashID-OrgID: &amp;lt;ORGANIZATION ID&amp;gt;&apos; \
--header &apos;SlashID-API-Key: &amp;lt;API KEY&amp;gt;&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;trigger_type&quot;: &quot;sync_hook&quot;,
    &quot;trigger_name&quot;: &quot;token_minted&quot;
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can process the webhook payload as follows (error checking removed for brevity):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
    jwks_client = jwt.PyJWKClient(&quot;https://api.slashid.com/organizations/webhooks/verification-jwks&quot;, headers={&quot;SlashID-OrgID&quot;: &quot;&amp;lt;ORGANIZATION ID&amp;gt;&quot;})
    webhookURL = &quot;&quot;

    request_data = request.get_data()
    header = jwt.get_unverified_header(request_data)
    key = jwks_client.get_signing_key(header[&quot;kid&quot;]).key
    try:
        header = jwt.get_unverified_header(request_data)
        key = jwks_client.get_signing_key(header[&quot;kid&quot;]).key
        token = jwt.decode(request_data, key, audience=&quot;&amp;lt;ORGANIZATION ID&amp;gt;&quot;, algorithms=[&quot;ES256&quot;])

        if token[&apos;target_url&apos;] != webhookURL:
            raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=f&quot;Token {token} is invalid: {e}&quot;,
        )
    except Exception as e:
        raise HTTPException(
          status_code=status.HTTP_401_UNAUTHORIZED,
          detail=f&quot;Token {token} is invalid: {e}&quot;,
      )

    # Now the request has been validated, let&apos;s extract the IP address

    ip_address = token[&apos;request_metadata&apos;][&apos;client_ip_address&apos;]
    print(f&quot;Ip address {ip_address}\n&quot;)

    # We use the SlashID external credentials to store the Seon API key
    seon_key = requests.get(&quot;https://api.slashid.com/organizations/config/external-credentials/856b7dec-d2c3-41ab-a151-c615925433e0&quot;, headers={&quot;SlashID-OrgID&quot;: &quot;&amp;lt;ORGANIZATION ID&amp;gt;&quot;, &quot;SlashID-API-Key&quot;: &quot;&amp;lt;SLASHID KEY&amp;gt;&quot;})
    seon_key = seon_key.json()
    seon_api_key_header = seon_key[&quot;result&quot;][&quot;json_blob&quot;]

    r = requests.get(f&quot;https://api.seon.io/SeonRestService/ip-api/v1.1/{ip_address}&quot;, headers=seon_api_key_header)
    seon_score = r.json()

    ## Check if the IP address is a known Tor IP address and if so, deny the request
    if seon_score[&apos;data&apos;][&apos;Tor&apos;]:
        return HTTPException(
          status_code=status.HTTP_401_UNAUTHORIZED,
          detail=f&quot;Bearer token {token} is invalid: {e}&quot;,
      )

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Allowing requests through but enforcing step-up auth for Tor Addresses&lt;/h2&gt;
&lt;p&gt;We follow the same procedure as above, but instead of returning a 401, we return a custom claim to add to the JWT token that&apos;s returned to the frontend:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...

return {
  &quot;tor_address&quot;: True,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The JWT returned to the frontend will contain a custom claim &lt;code&gt;tor_address&lt;/code&gt; and upon token inspection we can use the &lt;code&gt;&amp;lt;StepUpAuth&amp;gt;&lt;/code&gt; component to force another factor if the user&apos;s IP comes from Tor.&lt;/p&gt;
&lt;h2&gt;Augmenting the token with marketing intelligence data&lt;/h2&gt;
&lt;p&gt;We can use the same approach as above to enrich the JWT token with marketing intelligence data on the lead - we&apos;ll use &lt;a href=&quot;https://clearbit.com/&quot;&gt;clearbit&lt;/a&gt; for this (error checking removed for brevity):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
    jwks_client = jwt.PyJWKClient(&quot;https://api.slashid.com/organizations/webhooks/verification-jwks&quot;, headers={&quot;SlashID-OrgID&quot;: &quot;&amp;lt;ORGANIZATION ID&amp;gt;&quot;})

    request_data = request.get_data()

    try:
        header = jwt.get_unverified_header(request_data)
        key = jwks_client.get_signing_key(header[&quot;kid&quot;]).key
        token = jwt.decode(request_data, key, audience=&quot;&amp;lt;ORGANIZATION ID&amp;gt;&quot;, algorithms=[&quot;ES256&quot;])
    except Exception as e:
        raise HTTPException(
          status_code=status.HTTP_401_UNAUTHORIZED,
          detail=f&quot;Token {token} is invalid: {e}&quot;,
      )

    # Now the request has been validated, let&apos;s extract the user handle (email address/phone number)
    handle = token[&apos;authentications&apos;][0][&apos;handle&apos;][&apos;value&apos;]
    print(f&quot;User handle {handle}\n&quot;)

    ip_address = token[&apos;request_metadata&apos;][&apos;client_ip_address&apos;]
    print(f&quot;Ip address {ip_address}\n&quot;)

    # We use the SlashID external credentials to store the Clearbit API key
    clearbit_key = requests.get(&quot;https://api.slashid.com/organizations/config/external-credentials/856b7dec-d2c3-41ab-a151-c615925444b0&quot;, headers={&quot;SlashID-OrgID&quot;: &quot;&amp;lt;ORGANIZATION ID&amp;gt;&quot;, &quot;SlashID-API-Key&quot;: &quot;&amp;lt;SLASHID KEY&amp;gt;&quot;})
    clearbit_key = clearbit_key.json()
    clearbit_api_key_header = clearbit_key[&quot;result&quot;][&quot;json_blob&quot;]

    ## Retrieve the person by IP address + email address
    r = requests.get(f&quot;https://person.clearbit.com/v2/people/find?email={handle}&amp;amp;ip_address={ip_address}&quot;, headers=clearbit_api_key_header)
    clearbit_response = r.json()

    return {
        &quot;inferred_company&quot;: clearbit_response[&apos;employment&apos;][&apos;name&apos;],
        &quot;inferred_seniority&quot;: clearbit_response[&apos;employment&apos;][&apos;seniority&apos;],
        &quot;inferred_title&quot;: clearbit_response[&apos;employment&apos;][&apos;title&apos;],
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The token returned in the frontend will contain the &lt;code&gt;inferred_company&lt;/code&gt;, &lt;code&gt;inferred_seniority&lt;/code&gt; and &lt;code&gt;inferred_title&lt;/code&gt; custom claims. You can then customize the UI flow depending on the persona.&lt;/p&gt;
&lt;h2&gt;Asynchronous vs Synchronous hooks&lt;/h2&gt;
&lt;p&gt;As discussed, SlashID exposes a number of &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/events/event_definitions&quot;&gt;events&lt;/a&gt; that can be hooked through webhooks. The examples above can also be executed asynchronously using the &lt;code&gt;PersonCreated_v1&lt;/code&gt; (registration) or &lt;code&gt;AuthenticationSucceeded_v1&lt;/code&gt; (authentication) events to collect analytics or trigger workflows (e.g.: an email campaign).&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this brief blog post, we&apos;ve shown how you can combine multiple features of SlashID to make informed decisions about your users - whether it&apos;s a custom conversion flow depending on the buyer persona, or a stronger authentication requirement for a potential bot.&lt;/p&gt;
&lt;p&gt;We’d love to hear any feedback you may have. Please contact us at &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;contact@slashid.dev&lt;/a&gt;! Try out SlashID with a &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=contextaware-bp&quot;&gt;free account&lt;/a&gt;.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>SlashID: Building a globally distributed Identity Platform</title><link>https://slashid.dev/blog/distributed-identity/</link><guid isPermaLink="true">https://slashid.dev/blog/distributed-identity/</guid><description>We built the SlashID infrastructure so that your user data is globally distributed. Our architecture helps applications using SlashID benefit from dramatically reduced latency, high availability and comply with data protection laws without fragmented identity silos or extra fees.</description><pubDate>Mon, 19 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;What do we mean by global distribution?&lt;/h2&gt;
&lt;p&gt;Global distribution means that you can store user data in any region where our cloud providers (currently GCP, more to come soon) are present.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Whether you want to store all or part of your userbase in Japan, Poland, Australia, or any other &lt;a href=&quot;https://cloud.google.com/about/locations&quot;&gt;region&lt;/a&gt;, we have you covered at no extra fee and without a complex multi-tenant, multi-dashboard setup.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A large portion of our engineering team cut their teeth at CrowdStrike where ThreatGraph processes several trillion events per week, so we understand that building large-scale cloud systems is a complex and challenging task. For this reason, we have spent over 18 months building and stress-testing our infrastructure.&lt;/p&gt;
&lt;p&gt;The SlashID multi-region environment has been live in production for over a year, with more than 3 million users across different regions and several hundred &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/suborgs&quot;&gt;organizations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Having evaluated several options including large-scale distributed databases like CockroachDB and PlanetScale, we believe our approach is unique and provides the lowest possible latency.&lt;/p&gt;
&lt;p&gt;In a future blog post we&apos;ll discuss the technical architecture in more detail but, for now, let&apos;s see why this matters and how to use it.&lt;/p&gt;
&lt;h2&gt;Why does global availability matter?&lt;/h2&gt;
&lt;h3&gt;Regulatory compliance&lt;/h3&gt;
&lt;p&gt;While not all user data is subject to data residency requirements, the regulatory environment is becoming progressively more stringent on data residency and protection.&lt;/p&gt;
&lt;p&gt;A non-exhaustive list of countries that enforce data residency requirements for some user data/PII includes China, Russia, Germany, India, Brazil, Australia, Canada, France, South Korea, and Turkey.&lt;/p&gt;
&lt;p&gt;Beyond current regulatory requirements, more and more customers prefer to keep their data close to their geography to future-proof their infrastructure in case of further regulatory or legal pressure.&lt;/p&gt;
&lt;p&gt;Specific sections of our documentation discuss our approach to compliance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/data-protection-compliance&quot;&gt;Data Protection and Privacy Laws&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/app_encryption&quot;&gt;App-layer cryptographic primitives for secure storage of user data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Availability, performance, and disaster recovery&lt;/h3&gt;
&lt;p&gt;Authentication and authorization are part of the most critical application paths including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logging in to your application&lt;/li&gt;
&lt;li&gt;Checking user access to a specific resource&lt;/li&gt;
&lt;li&gt;Online JSON Web Token (JWT) verification and session management&lt;/li&gt;
&lt;li&gt;Retrieving and storing user data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;End-users will immediately notice an outage in these services. If your identity provider is down, your entire application becomes unavailable.&lt;/p&gt;
&lt;p&gt;We built SlashID’s identity and data storage around the principles of availability, performance, and disaster recovery from day 1. In this blog post, we&apos;ll cover:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#the-drawbacks-of-single-region-deployments&quot;&gt;The drawbacks of single-region deployments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#slashid-global-deployment-with-the-straightforwardness-of-single-region-deployment&quot;&gt;SlashID&apos;s developer experience for handling multi-region&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a future blog post, we&apos;ll discuss the technical aspects of how we built our infrastructure.&lt;/p&gt;
&lt;h2&gt;The drawbacks of single-region deployments&lt;/h2&gt;
&lt;p&gt;First, let&apos;s take a look at the downsides of using a single-region deployment.&lt;/p&gt;
&lt;h3&gt;Latency impact&lt;/h3&gt;
&lt;p&gt;One of the biggest downsides of a single-region deployment is the increased latency for end-users.&lt;/p&gt;
&lt;p&gt;Users have become accustomed to edge deployment and low latency whenever they use web apps and services. They expect the same level of performance when it comes to authentication.
However, legacy Identity and Access Management (IAM) solutions with single-region deployments are affected by significant latency.&lt;/p&gt;
&lt;p&gt;But what does ‘significant’ mean here? To give you a better perspective, we&apos;ve measured cross-region latency across multiple locations.&lt;/p&gt;
&lt;p&gt;We run this test using &lt;code&gt;netperf&lt;/code&gt;, a popular benchmark tool that can be used to measure the performance of many different types of networking. All machines except one were deployed in the Google Cloud Platform (GCP). One machine was deployed in Digital Ocean in Frankfurt to simulate cross-data center calls within one geographical location.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;table-wide&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data Center&lt;/th&gt;
&lt;th&gt;Frankfurt (GCP)&lt;/th&gt;
&lt;th&gt;Frankfurt (Digital Ocean)&lt;/th&gt;
&lt;th&gt;London&lt;/th&gt;
&lt;th&gt;Montreal&lt;/th&gt;
&lt;th&gt;Los Angeles&lt;/th&gt;
&lt;th&gt;Sydney&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frankfurt (GCP)&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;0.95ms&lt;/td&gt;
&lt;td&gt;13.25ms&lt;/td&gt;
&lt;td&gt;87.56ms&lt;/td&gt;
&lt;td&gt;152.84ms&lt;/td&gt;
&lt;td&gt;281.65ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frankfurt (Digital Ocean)&lt;/td&gt;
&lt;td&gt;1.09ms&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;12.37ms&lt;/td&gt;
&lt;td&gt;89.00ms&lt;/td&gt;
&lt;td&gt;153.06ms&lt;/td&gt;
&lt;td&gt;262.35ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;London&lt;/td&gt;
&lt;td&gt;12.99ms&lt;/td&gt;
&lt;td&gt;13.7ms&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;77.46ms&lt;/td&gt;
&lt;td&gt;139.68ms&lt;/td&gt;
&lt;td&gt;267.06ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Montreal&lt;/td&gt;
&lt;td&gt;88.57ms&lt;/td&gt;
&lt;td&gt;89.16ms&lt;/td&gt;
&lt;td&gt;78.13ms&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;75.52ms&lt;/td&gt;
&lt;td&gt;199.45ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Los Angeles&lt;/td&gt;
&lt;td&gt;152.46ms&lt;/td&gt;
&lt;td&gt;151.50ms&lt;/td&gt;
&lt;td&gt;142.34ms&lt;/td&gt;
&lt;td&gt;75.11ms&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;138.27ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sydney&lt;/td&gt;
&lt;td&gt;281.33ms&lt;/td&gt;
&lt;td&gt;265.31ms&lt;/td&gt;
&lt;td&gt;272.41ms&lt;/td&gt;
&lt;td&gt;199.69ms&lt;/td&gt;
&lt;td&gt;138.26ms&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;In the best-case scenario, an HTTP call between Europe and North America (London to Montreal) adds &lt;strong&gt;77 ms&lt;/strong&gt; of latency per request.
The numbers reported in the table above show the overhead of a single cross-region call: each request back and forth incurs that penalty on top of its normal execution time. Latency scales linearly with the number of requests, so performance deteriorates further when multiple calls are executed.&lt;/p&gt;
&lt;p&gt;For instance, let&apos;s consider a scenario where a request is made between data centers in Europe (Frankfurt) and the West Coast (Los Angeles). In this scenario, &lt;strong&gt;each request incurs 152ms of overhead&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;While it may not look like much, in practice, when you also account for the execution time of the request, you&apos;ll often exceed 200ms. It is well documented that latency above 200ms is perceived by the user and contributes to user drop-off.&lt;/p&gt;
&lt;p&gt;The performance impact also becomes evident when a user persists in one region and travels to another.&lt;/p&gt;
&lt;h3&gt;Risk of regional outages&lt;/h3&gt;
&lt;p&gt;Last year alone, the biggest cloud providers suffered from multiple major outages, often affecting entire regions.
&lt;strong&gt;US-EAST-1 AWS (the biggest AWS US region) experienced a 6-hour outage. A flood tanked a GCP region for 12 whole hours.&lt;/strong&gt;
Most companies using AWS and GCP to host their services were not prepared for these incidents and, as a result, suffered global outages for many hours. You might have noticed several tools and products not working during those days.&lt;/p&gt;
&lt;p&gt;It&apos;s hard (and beyond the scope of this blog post) to quantify the financial and reputational damage those products suffered during the AWS and GCP outages. However, the positive outcome is that it triggered multiple discussions about the importance of having a strong multi-region strategy.
&lt;strong&gt;Unfortunately, multi-region deployment is not a quick and easy fix: it is hard to get right and, when not implemented from the get-go, it often requires a lot of architectural changes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Regional outages of core infrastructure have occurred in the past. They will undoubtedly occur again.
Using external services (e.g., identity providers) that are resilient to such outages should be part of every solid multi-region strategy.&lt;/p&gt;
&lt;h3&gt;Compliance&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, depending on the countries you operate in, you may be subject to data residency or protection laws requiring user data to be localized wholly or in part in a given region.&lt;/p&gt;
&lt;p&gt;A single region deployment model would require you to spawn a separate instance of your services for each country you operate in, making implementation, customer support, and analytics significantly more challenging.&lt;/p&gt;
&lt;h2&gt;SlashID: Global deployment with the ease of single-region deployment&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;We built SlashID as a global identity platform deployed and replicated across GCP&apos;s regions to achieve best-in-class resiliency with the lowest latency and highest availability.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Our infrastructure can bootstrap a new region in approximately 24 hours and we are present by default in 5 regions.&lt;/p&gt;
&lt;p&gt;We want to give you the best developer experience, which also means handling all cross-region concerns, so you won’t have to.
&lt;strong&gt;As such, you can access cross-region functionality with the same abstraction as common single-region APIs.&lt;/strong&gt;
Unless specified, users will automatically be assigned to the region closest to them, minimizing latency and maximizing performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We designed our system so that each region is autonomous.&lt;/strong&gt;
This reduces the chance of a global outage. If there is no connectivity between regions (due to network issues or regional outage), all regions will keep on working independently, and will eventually synchronize the data after network connections are restored.&lt;/p&gt;
&lt;h3&gt;How does it work in practice?&lt;/h3&gt;
&lt;p&gt;By default, when a user is created, their data will be stored in the region geographically closest to the request to our API.
&lt;strong&gt;Data residency is transparent for you: you can store and retrieve data without thinking about where it&apos;s stored.&lt;/strong&gt;
SlashID will either read the data directly from the region where it&apos;s stored or use a copy of the data replicated in a closer location.&lt;/p&gt;
&lt;p&gt;You can try it yourself with the HTTP requests we saw earlier:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X POST &apos;https://api.slashid.com/persons&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
--data-raw &apos;{
  &quot;handles&quot;: [
    {
      &quot;type&quot;: &quot;email_address&quot;,
      &quot;value&quot;: &quot;user@user.com&quot;
    }
  ],
  &quot;attributes&quot;: {
    &quot;end_user_no_access&quot;: {
      &quot;is_vip&quot;: true,
      &quot;secret_note&quot;: &quot;this person is our VIP&quot;
    },
    &quot;end_user_read_write&quot;: {
      &quot;address&quot;: {
        &quot;city&quot;: &quot;New York&quot;,
        &quot;country&quot;: &quot;USA&quot;,
        &quot;postal_code&quot;: &quot;10001&quot;,
        &quot;street&quot;: &quot;123 Main Street&quot;
      },
      &quot;credit_card_number&quot;: &quot;1234-1234-1234-1234&quot;
    }
  }
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The response will contain the person ID and the person&apos;s region:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;result&quot;: {
    &quot;active&quot;: true,
    &quot;person_id&quot;: &quot;064d221c-b5cb-7c37-a908-0120768e52f5&quot;,
    &quot;region&quot;: &quot;us-iowa&quot;,
    &quot;roles&quot;: []
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;As shown earlier, even if requests are traveling between data centers in the same geographical location, the latency overhead is nominal (~1 ms).
Thanks to that, you can call the SlashID API with a latency similar to calling your internal API.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you want, you can also specify where the person&apos;s data should be stored explicitly. For example, let&apos;s create a person in Japan:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X POST &apos;https://api.slashid.com/persons&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
--data-raw &apos;{
  &quot;handles&quot;: [
    {
      &quot;type&quot;: &quot;email_address&quot;,
      &quot;value&quot;: &quot;user-in-japan@user.jp&quot;
    }
  ],
  &quot;region&quot;: &quot;asia-japan&quot;
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;result&quot;: {
    &quot;active&quot;: true,
    &quot;person_id&quot;: &quot;064d2234-79b9-74f4-a810-061d691e84dd&quot;,
    &quot;region&quot;: &quot;asia-japan&quot;,
    &quot;roles&quot;: []
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;To achieve a multi-region setup with other Identity Providers, you must configure a separate project or organization for each region.&lt;/strong&gt;
In such a case, data retrieval and synchronization become difficult and error-prone: you will usually need to know which organization or project must be used to query a particular user.
In the worst case, when you only have a user ID and don’t know where the data resides, you will need to iterate over all regions to find it.&lt;/p&gt;
&lt;p&gt;When using SlashID, you can retrieve a person&apos;s data without thinking about where it&apos;s stored:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X GET &apos;https://api.slashid.com/persons/064d2234-79b9-74f4-a810-061d691e84dd&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;result&quot;: {
    &quot;active&quot;: true,
    &quot;person_id&quot;: &quot;064d2234-79b9-74f4-a810-061d691e84dd&quot;,
    &quot;region&quot;: &quot;asia-japan&quot;,
    &quot;roles&quot;: []
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same applies to user attributes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X GET &apos;https://api.slashid.com/persons/064d2250-acf8-74bd-a308-e4ecda7c8bf4/attributes&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;result&quot;: {
    &quot;end_user_no_access&quot;: {
      &quot;is_vip&quot;: true,
      &quot;secret_note&quot;: &quot;this person is our boss&apos;s friend&quot;
    },
    &quot;end_user_read_write&quot;: {
      &quot;address&quot;: {
        &quot;city&quot;: &quot;New York&quot;,
        &quot;country&quot;: &quot;USA&quot;,
        &quot;postal_code&quot;: &quot;10001&quot;,
        &quot;street&quot;: &quot;123 Main Street&quot;
      },
      &quot;credit_card_number&quot;: &quot;1234-1234-1234-1234&quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;ve also optimized for the corner case where at a point in time a user is far from the region where their data is stored. We&apos;ll describe that in detail in an upcoming blog post focused on the multi-region architecture.&lt;/p&gt;
&lt;h3&gt;Replicating configuration&lt;/h3&gt;
&lt;p&gt;The other burden of manually dealing with multi-region deployments is handling proper configuration. For instance, messaging templates, authentication and authorization policies, and SAML/OIDC providers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maintaining separate projects or organizations per region adds significant configuration overhead.&lt;/strong&gt;
&lt;strong&gt;Additionally, when you maintain separate projects or organizations for each region, you must synchronize their configurations.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In SlashID, all organizations are global by design. There is no need to have an organization or a project for each region.&lt;/strong&gt;
You update the configuration in the same way as you would for a single-region organization.
All the complexity of replicating organizations globally is handled by SlashID.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X PATCH &apos;https://api.slashid.com/organizations/config&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos;
--data-raw &apos;{
  &quot;token_duration&quot;: 86400
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, we store the configuration in the region from which the HTTP call originated synchronously. Other regions are updated asynchronously.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/distributed-identity/config-replication.svg&quot; alt=&quot;Diagram Config&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Sometimes, knowing that the configuration was stored in a single region is not enough and SlashID provides the ability to wait synchronously for updates to be propagated.
It may be useful when you want to make sure that the configuration is stored in all regions before you proceed with another configuration update.
It&apos;s especially important when your product has a global reach.&lt;/p&gt;
&lt;p&gt;You can choose to wait until the configuration is stored in all regions, by using &lt;code&gt;SlashID-Required-Consistency: all_regions&lt;/code&gt; header:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X PATCH &apos;https://api.slashid.com/organizations/config&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
-H &apos;SlashID-Required-Consistency: all_regions&apos; \
-H &apos;SlashID-Required-Consistency-Timeout: 15&apos; \
--data-raw &apos;{
  &quot;token_duration&quot;: 86400
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/blog/distributed-identity/config-replication-sync.svg&quot; alt=&quot;Diagram Config&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While global configuration replication typically completes in less than half a second, it can sometimes take a few seconds.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/distributed-identity/org-update-trace.png&quot; alt=&quot;Org update trace&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you decide not to wait for global replication, our model propagates the organization structure with eventual consistency.&lt;/p&gt;
&lt;h3&gt;The UI&lt;/h3&gt;
&lt;p&gt;Lastly, you can see all your users in the same dashboard (with role-based access control for different regions if compliance is a priority) without switching between multiple systems or tenants to make analytics and customer support easier.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/distributed-identity/redacted-dashboard.png&quot; alt=&quot;Dashboard&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;What&apos;s next?&lt;/h2&gt;
&lt;p&gt;If you haven&apos;t tried it yet, we recommend you &lt;a href=&quot;https://console.slashid.dev/signup?bp=multi-region&quot;&gt;create a free account&lt;/a&gt; in SlashID and see for yourself what our &lt;a href=&quot;https://developer.slashid.dev/?bp=multiregion&quot;&gt;API and SDK&lt;/a&gt; can do.&lt;/p&gt;
&lt;p&gt;Explore our &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/replication&quot;&gt;replication documentation&lt;/a&gt; to dig deeper into the technical details of our replication model.&lt;/p&gt;
&lt;p&gt;If you like our solution and are considering migrating your identity system, test it out with a &lt;a href=&quot;https://console.slashid.dev/signup?bp=multi-region&quot;&gt;free account&lt;/a&gt; or reach out to us for any questions or comments at contact@slashid.dev.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, Robert Laszczak</author></item><item><title>Building a React Login Page Template</title><link>https://slashid.dev/blog/building-a-react-login-page-template/</link><guid isPermaLink="true">https://slashid.dev/blog/building-a-react-login-page-template/</guid><description>Discover how to create a secure login page for your React app with authentication and styling using SlashID. </description><pubDate>Sun, 16 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Quickstart&lt;/h2&gt;
&lt;p&gt;In this tutorial, we’ll build an essential (but functional) React application to explore the capabilities of the SlashID React SDK. If you want to check out the SlashID React SDK in ~15 minutes, this tutorial is for you. You can also find the complete source code of this tutorial in our &lt;a href=&quot;https://codesandbox.io/s/react-quickstart-tutorial-qu1knw&quot;&gt;CodeSandbox&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Goal&lt;/h2&gt;
&lt;p&gt;You will build a simple UI that handles user authentication and user data storage using the SlashID React SDK. Along the way, we will touch several concepts that are key to understanding our SDK, such as Organizations and Attribute Buckets.&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before starting, you should &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=reactlogin&quot;&gt;sign up&lt;/a&gt; to SlashID and create your first organization and retrieve the &lt;code&gt;ORGANIZATION_ID&lt;/code&gt;, required to complete this tutorial.&lt;/p&gt;
&lt;p&gt;If you don’t have them already, now’s the time to install:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/en/&quot;&gt;Node.js&lt;/a&gt; version 16.0.0 or greater&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/&quot;&gt;npm&lt;/a&gt; 7 or greater&lt;/li&gt;
&lt;li&gt;A code editor of your choice&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Like our Core SDK, all examples in this tutorial use &lt;a href=&quot;https://www.typescriptlang.org/&quot;&gt;TypeScript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;SlashID ❤️ TypeScript!&lt;/p&gt;
&lt;h2&gt;1. Set up the project&lt;/h2&gt;
&lt;p&gt;First, create a new React app. We’ll use &lt;a href=&quot;https://vitejs.dev/&quot;&gt;Vite&lt;/a&gt; to bootstrap the project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm create vite@latest slashid-demo -- --template react-ts
cd slashid-demo &amp;amp;&amp;amp; npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, run the dev server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should now have a React application running on your local machine.&lt;/p&gt;
&lt;p&gt;Last, let’s do some cleanup in &lt;code&gt;src/App.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function App() {
  return &amp;lt;div&amp;gt;It works!&amp;lt;/div&amp;gt;
}

export default App
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, feel free to grab this CSS snippet:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;
  color: #213547;
  background-color: #e5e5e5;
}

body {
  margin: 0;
  display: flex;
  place-items: center;
  min-width: 320px;
  min-height: 100vh;
}

#root {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  display: flex;
  justify-content: center;
}

.formWrapper {
  width: 390px;
  margin: 32px;
}

.storage {
  box-sizing: border-box;
  background-color: #ffffff;
  background: #ffffff;
  border: 1px solid rgba(20, 32, 73, 0.06);
  box-shadow: 0px 12px 24px rgba(29, 25, 77, 0.03);
  border-radius: 32px;
  padding: 16px;
  min-width: 390px;
  padding: 32px;
}

.storage form {
  display: flex;
  flex-direction: column;
}

.storage form &amp;gt; input {
  margin-bottom: 16px;
  padding: 12px 16px;
  font-size: 16px;
  line-height: 122%;
  background: rgba(20, 32, 73, 0.01);
  border: 1px solid rgba(20, 32, 73, 0.06);
  border-radius: 12px;
}

.storage form &amp;gt; button[type=&apos;submit&apos;] {
  margin-bottom: 8px;
  padding: 16px 22px;
  font-size: 16px;
  font-weight: 600;
  line-height: 122%;
  background:
    linear-gradient(0deg, rgba(15, 14, 27, 0.1), rgba(15, 14, 27, 0.1)), #2a6aff;
  border-radius: 12px;
  outline: none;
  border: none;
  color: #ffffff;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point your &lt;code&gt;src&lt;/code&gt; directory should contain the following files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src
|-- App.tsx
|-- index.css
|-- main.tsx
`-- vite-env.d.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great, you’re now good to go! Let’s build something cool using that SlashID magic.&lt;/p&gt;
&lt;h2&gt;2. Initialize the SDK&lt;/h2&gt;
&lt;p&gt;First, install the SlashID React SDK.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Since the React SDK is built around the Core SlashID SDK, it must also be installed as a peer dependency.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;npm install @slashid/slashid @slashid/react
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the dependencies are installed, we need to wrap our application with &lt;code&gt;&amp;lt;SlashIDProvider&amp;gt;&lt;/code&gt;. This ensures that all the components have access to the SlashID Context, most importantly the &lt;code&gt;sid&lt;/code&gt; object, which is our stable reference to the Core SDK. Make sure to check the &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-slashidprovider&quot;&gt;documentation&lt;/a&gt; for more details!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You must request a demo SlashID account in order to get your &lt;code&gt;ORGANIZATION_ID&lt;/code&gt; (See &lt;a href=&quot;#prerequisites&quot;&gt;Prerequisites&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;import ReactDOM from &quot;react-dom/client&quot;;
import { SlashIDProvider } from &quot;@slashid/react&quot;;
import App from &quot;./App&quot;;
import &quot;./index.css&quot;;

ReactDOM.createRoot(document.getElementById(&quot;root&quot;) as HTMLElement).render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;SlashIDProvider oid=&quot;ORGANIZATION_ID&quot;&amp;gt;
      &amp;lt;App /&amp;gt;
    &amp;lt;/SlashIDProvider&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your application should now be connected to SlashID through the Core SDK. To verify it, you can try accessing it through the &lt;code&gt;useSlashID()&lt;/code&gt; hook included in the React SDK.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useSlashID } from &apos;@slashid/react&apos;

function App() {
  const { sid } = useSlashID()
  return &amp;lt;div&amp;gt;{sid ? &apos;It works!&apos; : &apos;SDK is being loaded….&apos;}&amp;lt;/div&amp;gt;
}

export default App
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Make sure that the SlashID SDK loads successfully before moving any further!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;3. Add Login functionality&lt;/h2&gt;
&lt;p&gt;Now, let’s add login capabilities to our application. The React SDK exposes a ready to use configurable &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-form&quot;&gt;Login Form&lt;/a&gt; component for this purpose.&lt;/p&gt;
&lt;p&gt;The Form comes with &lt;code&gt;&amp;lt;ConfigurationProvider /&amp;gt;&lt;/code&gt; to easily customize some UI properties, such as &lt;code&gt;theme&lt;/code&gt; and form &lt;code&gt;factors&lt;/code&gt;. You can read more about SlashID Login Form customization &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-configurationprovider&quot;&gt;here&lt;/a&gt;. You can choose from a variety of authentication factors, such as &lt;a href=&quot;https://webauthn.io/&quot;&gt;Passkeys (WebAuthn)&lt;/a&gt;, SMS or SSO with third party providers like Google or Facebook. Let’s start with a basic onboarding form setup, choosing to only allow login with email via magic link.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom/client&quot;;
import { SlashIDProvider, ConfigurationProvider } from &quot;@slashid/react&quot;;
import { Factor } from &quot;@slashid/slashid&quot;;
import App from &quot;./App&quot;;
import &quot;./index.css&quot;;

const factors: Factor[] = [{ method: &quot;email_link&quot; }];

ReactDOM.createRoot(document.getElementById(&quot;root&quot;) as HTMLElement).render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;SlashIDProvider oid=&quot;ORGANIZATION_ID&quot;&amp;gt;
      &amp;lt;ConfigurationProvider factors={factors}&amp;gt;
        &amp;lt;App /&amp;gt;
      &amp;lt;/ConfigurationProvider&amp;gt;
    &amp;lt;/SlashIDProvider&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;
);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SlashID React SDK also provides first-class UI components to help you render different flows for authenticated and not authenticated users. Let’s use those as well!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { LoggedIn, LoggedOut, Form } from &apos;@slashid/react&apos;

import &apos;@slashid/react/style.css&apos;

function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;LoggedOut&amp;gt;
        &amp;lt;div className=&quot;formWrapper&quot;&amp;gt;
          &amp;lt;Form /&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/LoggedOut&amp;gt;
      &amp;lt;LoggedIn&amp;gt;You&apos;re authenticated!&amp;lt;/LoggedIn&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default App
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can authenticate into your React app using the email address associated with your SlashID organization.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://developer.slashid.dev/assets/images/login-form-332de98190ddda060c17251a660f2979.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When you click the &lt;code&gt;Continue&lt;/code&gt; button, you should receive an email from SlashID with a magic link. Use the link to complete the authentication process. After that, return to your React application - you should be authenticated now!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Make sure that you can authenticate using your email before you proceed!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;4. Store authenticated user data&lt;/h2&gt;
&lt;p&gt;You can build a simple key-value storage for authenticated users using SlashID’s Data Vault. The &lt;code&gt;useSlashID()&lt;/code&gt; hook exposes the &lt;code&gt;user&lt;/code&gt; object: in the first instance it’s &lt;code&gt;undefined&lt;/code&gt;, but once you authenticate, it becomes your interface to SlashID Attributes and Multi-Factor Authentication APIs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can learn more about Data Vault and Attribute Buckets on our &lt;a href=&quot;https://developer.slashid.dev/&quot;&gt;developer portal&lt;/a&gt; 🤓&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Before implementing the &lt;code&gt;&amp;lt;Storage /&amp;gt;&lt;/code&gt; component, let’s make development with SlashID a little easier. &lt;code&gt;&amp;lt;SlashIDProvider /&amp;gt;&lt;/code&gt; stores the User token in memory by default. This means you lose the information about the authenticated user every time the page reloads – not the most convenient development (or user) experience. You can override this setting using the &lt;code&gt;tokenStorage&lt;/code&gt; prop on the provider.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom/client&quot;;
import { SlashIDProvider, ConfigurationProvider } from &quot;@slashid/react&quot;;
import { Factor } from &quot;@slashid/slashid&quot;;
import App from &quot;./App&quot;;
import &quot;./index.css&quot;;

const factors: Factor[] = [{ method: &quot;email_link&quot; }];

ReactDOM.createRoot(document.getElementById(&quot;root&quot;) as HTMLElement).render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;SlashIDProvider
       oid=&quot;ORGANIZATION_ID&quot;
       tokenStorage=&quot;localStorage&quot;
    &amp;gt;
      &amp;lt;ConfigurationProvider factors={factors}&amp;gt;
        &amp;lt;App /&amp;gt;
      &amp;lt;/ConfigurationProvider&amp;gt;
    &amp;lt;/SlashIDProvider&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this way, the user token obtained from a successful authentication will be persisted in Local Storage, and you no longer need to authenticate after each full page reload. Sweet!&lt;/p&gt;
&lt;p&gt;Let’s build our &lt;code&gt;&amp;lt;Storage /&amp;gt;&lt;/code&gt; component with simple storage capabilities:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useSlashID } from &quot;@slashid/react&quot;;
import { useEffect, useState, SyntheticEvent } from &quot;react&quot;;

function Storage() {
  // User object reference - only available after successful authentication
  const { user } = useSlashID({ oid: &quot;ORGANIZATION_ID&quot; });
  // React state for rendering our attributes stored in Data Vault
  const [attributes, setAttributes] = useState&amp;lt;Record&amp;lt;string, string&amp;gt;&amp;gt;({});
  // Simple form state to enable storing new attributes
  const [newAttrName, setNewAttrName] = useState&amp;lt;string&amp;gt;(&quot;&quot;);
  const [newAttrValue, setNewAttrValue] = useState&amp;lt;string&amp;gt;(&quot;&quot;);

  // after authentication, fetch the attributes from Data Vault
  useEffect(() =&amp;gt; {
    async function fetchUserAttributes() {
      // getBucket takes in an optional `bucketName | string` argument
      // if not present, it will return the default Read/Write bucket
      const bucket = user?.getBucket();
      // calling bucket.get() with no arguments will return all attributes stored in this bucket
      const attrs = await bucket?.get&amp;lt;Record&amp;lt;string, string&amp;gt;&amp;gt;();

      setAttributes(attrs!);
    }

    fetchUserAttributes();
  }, [user]);

  const addNewAttribute = async () =&amp;gt; {
    // store new attribute
    const bucket = user?.getBucket();
    await bucket?.set({ [newAttrName]: newAttrValue });

    // simple refetch logic to re-render updated attributes list
    const attrs = await bucket?.get&amp;lt;Record&amp;lt;string, string&amp;gt;&amp;gt;();
    setAttributes(attrs!);

    // reset the form
    setNewAttrName(&quot;&quot;);
    setNewAttrValue(&quot;&quot;);
  };

  const handleSubmit = (e: SyntheticEvent) =&amp;gt; {
    e.preventDefault();

    addNewAttribute();
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;main className=&quot;storage&quot;&amp;gt;
        &amp;lt;h2&amp;gt;Stored attributes&amp;lt;/h2&amp;gt;
        {Object.keys(attributes).length === 0 ? (
          &amp;lt;p&amp;gt;Looks like there&apos;s nothing in here!&amp;lt;/p&amp;gt;
        ) : null}
        &amp;lt;ul&amp;gt;
          {/* display attributes from Data Vault as list items */}
          {Object.entries(attributes).map(([key, value]) =&amp;gt; (
            &amp;lt;li key={key}&amp;gt;
              {key}: {value}
            &amp;lt;/li&amp;gt;
          ))}
        &amp;lt;/ul&amp;gt;
        {/* minimal form for storing new attributes */}
        &amp;lt;form method=&quot;post&quot; onSubmit={handleSubmit}&amp;gt;
          &amp;lt;input
            value={newAttrName}
            placeholder=&quot;Attribute name&quot;
            onChange={(e) =&amp;gt; setNewAttrName(e.target.value)}
          /&amp;gt;
          &amp;lt;input
            value={newAttrValue}
            placeholder=&quot;Attribute value&quot;
            onChange={(e) =&amp;gt; setNewAttrValue(e.target.value)}
          /&amp;gt;
          &amp;lt;button type=&quot;submit&quot;&amp;gt;Add attribute&amp;lt;/button&amp;gt;
        &amp;lt;/form&amp;gt;
      &amp;lt;/main&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Storage;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To ensure that only the data of authenticated users is stored in Data Vault (and to make sure you have access to the &lt;code&gt;user&lt;/code&gt; property), you can put the &lt;code&gt;&amp;lt;Storage /&amp;gt;&lt;/code&gt; component inside the &lt;code&gt;&amp;lt;LoggedIn&amp;gt;&lt;/code&gt; wrapper:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { LoggedIn, LoggedOut, Form } from &apos;@slashid/react&apos;
import Storage from &apos;./Storage&apos;

import &apos;@slashid/react/style.css&apos;

function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;LoggedOut&amp;gt;
        &amp;lt;div className=&quot;formWrapper&quot;&amp;gt;
          &amp;lt;Form /&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/LoggedOut&amp;gt;
      &amp;lt;LoggedIn&amp;gt;
        &amp;lt;Storage /&amp;gt;
      &amp;lt;/LoggedIn&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default App
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Make sure that you can add new attributes to the default bucket with the &lt;code&gt;&amp;lt;User /&amp;gt;&lt;/code&gt; component!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Congratulations! You just built your first React application with the SlashID React SDK! Make sure to see the &lt;a href=&quot;https://developer.slashid.dev/docs/intro&quot;&gt;Docs page&lt;/a&gt; for a more detailed overview of the core concepts, as well as some in-depth guides. We hope you love SlashID!&lt;/p&gt;
</content:encoded><author>Ivan Kovic, Vincenzo Iozzo</author></item><item><title>Ditch your organizations table</title><link>https://slashid.dev/blog/ditch-orgs/</link><guid isPermaLink="true">https://slashid.dev/blog/ditch-orgs/</guid><description>Suborgs make it effortless and secure to implement complex identity structures such as multi-tenancy B2B apps and multi sided marketplaces.</description><pubDate>Tue, 12 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;You can find an example multi-tenancy note-taking application with sharing functionalities using SlashID on &lt;a href=&quot;https://github.com/slashid/org-switch-demo-public&quot;&gt;GitHub&lt;/a&gt; or &lt;a href=&quot;https://multitenancy-demo.slashid.dev/&quot;&gt;live preview&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&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&apos;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. 
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.
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;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;/li&gt;
&lt;li&gt;
&lt;p&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;/p&gt;
&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;
&lt;p&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;/p&gt;
&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;&lt;/p&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=&quot;https://salt.security/blog/traveling-with-oauth-account-takeover-on-booking-com&quot;&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;img src=&quot;/blog/ditch-orgs/im_just_trying_to_change_this_lightbulb.gif&quot; alt=&quot;Just changing lightbulbs&quot; /&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=&quot;https://developer.slashid.dev/docs/access/guides/suborgs&quot;&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 find an example multi-tenancy note-taking application with sharing functionalities using SlashID on &lt;a href=&quot;https://github.com/slashid/org-switch-demo-public&quot;&gt;GitHub&lt;/a&gt; or &lt;a href=&quot;https://multitenancy-demo.slashid.dev/&quot;&gt;live preview&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also find more examples in our &lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/suborgs&quot;&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;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/organizations/suborganizations&apos; \
--header &apos;SlashID-OrgID: &amp;lt;ORGANIZATION_ID&amp;gt;&apos; \
--header &apos;SlashID-API-Key: &amp;lt;API_KEY&amp;gt;&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;sub_org_name&quot;: &quot;Parks &amp;amp; Rec Dept&quot;,
    &quot;groups_org_id&quot;: &quot;85637a0a-a574-326a-bac3-d1f46d62dbd9&quot;
}&apos;
&lt;/code&gt;&lt;/pre&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
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;/p&gt;
&lt;pre&gt;&lt;code&gt;{
   &quot;result&quot;: {
       &quot;api_key&quot;: &quot;wY7gDtUDjxGMdynd6BKaaLojHFE=&quot;, // API key of the new suborganization
       &quot;id&quot;: &quot;97724371-a0a1-4b93-bf51-6ba2feb1acdf&quot;, // ID of the new suborganization
       &quot;org_name&quot;: &quot;Parks &amp;amp; Rec Dept&quot;, // as in the request
       &quot;tenant_name&quot;: “Trackalot&quot; // same as for the parent organization
   }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s create another one:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/organizations/suborganizations&apos; \
--header &apos;SlashID-OrgID: &amp;lt;ORGANIZATION_ID&amp;gt;&apos; \
--header &apos;SlashID-API-Key: &amp;lt;API_KEY&amp;gt;&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;sub_org_name&quot;: &quot;Dunder Mifflin Paper Company&quot;,
    &quot;groups_org_id&quot;: &quot;85637a0a-a574-326a-bac3-d1f46d62dbd9&quot;

}&apos;


{
    &quot;result&quot;: {
        &quot;api_key&quot;: &quot;FkImXyWgZhuqTGTh70fXzuI1PMo=&quot;,
        &quot;id&quot;: &quot;d89e29fc-2693-d3b7-764c-debc9561eea2&quot;,
        &quot;Org_name&quot;: &quot;Dunder Mifflin Paper Company&quot;,
        &quot;tenant_name&quot;: &quot;Trackalot&quot; // same as for the parent organization
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have built the following org structure:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/ditch-orgs/orgs_blue.jpeg&quot; alt=&quot;Suborgs&quot; /&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=&quot;https://developer.slashid.dev/docs/category/api/organizations&quot;&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;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/organizations/suborganizations&apos; \
--header &apos;SlashID-OrgID: 97724371-a0a1-4b93-bf51-6ba2feb1acdf&apos; \
--header &apos;SlashID-API-Key: wY7gDtUDjxGMdynd6BKaaLojHFE=&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;email_authn_challenge&quot;: &amp;lt;EMAIL_TEMPLATE&amp;gt;
}&apos;
&lt;/code&gt;&lt;/pre&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=&quot;https://developer.slashid.dev/docs/api/post-groups&quot;&gt;SlashID groups API&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/groups&apos; \
--header &apos;SlashID-OrgID: 85637a0a-a574-326a-bac3-d1f46d62dbd9&apos; \ // &quot;Trackalot&quot; org ID
--header &apos;SlashID-API-Key: /cgMbOUYjFGMUv4hIvKoMxhVIJ0=&apos; \ // &quot;Trackalot&quot; API key
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;name&quot;: &quot;employee&quot;
}&apos;

curl -X POST --location &apos;https://api.slashid.com/groups&apos; \
--header &apos;SlashID-OrgID: 85637a0a-a574-326a-bac3-d1f46d62dbd9&apos; \ // &quot;Trackalot&quot; org ID
--header &apos;SlashID-API-Key: /cgMbOUYjFGMUv4hIvKoMxhVIJ0=&apos; \ // &quot;Trackalot&quot; API key
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;name&quot;: &quot;admin&quot;
}&apos;
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X GET --location &apos;https://api.slashid.com/groups&apos; \
--header &apos;SlashID-OrgID: d89e29fc-2693-d3b7-764c-debc9561eea2&apos; \ // Dunder Mifflin Paper Company org ID
--header &apos;SlashID-API-Key: FkImXyWgZhuqTGTh70fXzuI1PMo=&apos; // Dunder Mifflin Paper Company API key

{
    &quot;result&quot;: [
        &quot;employee&quot;,
        &quot;admin&quot;
    ]
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X GET --location &apos;https://api.slashid.com/groups&apos; \
--header &apos;SlashID-OrgID: 97724371-a0a1-4b93-bf51-6ba2feb1acdf&apos; \ // Parks &amp;amp; Rec Dept org ID
--header &apos;SlashID-API-Key: wY7gDtUDjxGMdynd6BKaaLojHFE=&apos; // Parks &amp;amp; Rec Dept API key

{
    &quot;result&quot;: [
        &quot;employee&quot;,
        &quot;admin&quot;
    ]
}
&lt;/code&gt;&lt;/pre&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=&quot;https://developer.slashid.dev/docs/access/guides/suborgs&quot;&gt;guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But it doesn&apos;t end there, thanks to our buckets you are also able to share &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/attribute_buckets&quot;&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=&quot;https://developer.slashid.dev/&quot;&gt;documentation&lt;/a&gt; or &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;reach out to us&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ready to get started with SlashID? &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=ditchorgs-bp&quot;&gt;Sign up here!&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Read more&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/suborgs&quot;&gt;More suborg use cases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.slashid.dev/blog/groups-react/&quot;&gt;Conditional RBAC with our React SDK&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>SlashID Team, Vincenzo Iozzo</author></item><item><title>Adding Identity to Docusaurus</title><link>https://slashid.dev/blog/docusaurus-identity/</link><guid isPermaLink="true">https://slashid.dev/blog/docusaurus-identity/</guid><description>Today we are releasing the docusaurus-slashid-login theme as well as a fork of docusaurus-openapi-docs. The slashid plugin enables you to add out of the box authentication to docusaurus. The docusaurus-openapi-docs fork allows you to autofill API keys data, API parameters and more through slashid user attributes.</description><pubDate>Sat, 12 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Interested in implementing access control for Docusaurus? Check out our integration guide &lt;a href=&quot;https://developer.slashid.dev/docs/access/integrations/docusaurus-login&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;What is Docusaurus?&lt;/h2&gt;
&lt;p&gt;Developed by Meta, &lt;a href=&quot;https://docusaurus.io/&quot;&gt;Docusaurus&lt;/a&gt; is a fully customizable, static site generator primarily used to publish documentation. It is built using React and was first released publicly in 2017.&lt;/p&gt;
&lt;p&gt;At SlashID, we love Docusaurus and use it for our &lt;a href=&quot;https://developer.slashid.dev/&quot;&gt;developer portal&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can find &lt;code&gt;docusaurus-slashid-login&lt;/code&gt; on &lt;a href=&quot;https://github.com/slashid/docusaurus-slashid-login/blob/main/packages/docusaurus-theme-slashid/README.md&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Adding Identity to Docusaurus&lt;/h2&gt;
&lt;p&gt;Like most developer-focused companies, we expose part of the functionality of our backend services through our REST APIs.&lt;/p&gt;
&lt;p&gt;To interact with our API, you must add two fields to the API request headers: an API Key and a SlashID Organization ID.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/docusaurus-identity/screenshot.png&quot; alt=&quot;SlashID Documentation Site&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Our goal was to improve the Developer Experience and get rid of the repetitive step of filling in the SlashID-OrgID and the API key when our customers use our developer portal.&lt;/p&gt;
&lt;p&gt;To solve this problem we decided to add an identity layer to Docusaurus and use our Data Vault attribute storage module to autofill API keys and REST API parameters for our customers.&lt;/p&gt;
&lt;p&gt;Today we are releasing the SlashID theme which adds authentication support to OpenAPI together with a fork of the Docusaurus OpenAPI plugin that reads user data and autofills parameters or headers that match any of the user attributes.&lt;/p&gt;
&lt;p&gt;While our use case is niche, you can also use the theme for other use cases - for instance, to serve internal documentation without exposing it publicly to the internet.&lt;/p&gt;
&lt;h2&gt;The developer experience&lt;/h2&gt;
&lt;p&gt;A developer can log into our portal either through one of the supported passwordless methods or through a DirectID invite link so they don’t have to authenticate at all.&lt;/p&gt;
&lt;p&gt;You can try it yourself on our developer &lt;a href=&quot;https://developer.slashid.dev/&quot;&gt;portal&lt;/a&gt;, please ask us for an API key!&lt;/p&gt;
&lt;p&gt;&lt;video style=&quot;width:100%&quot; autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/blog/docusaurus-identity/slashid-docs-login.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&gt;&lt;/p&gt;
&lt;h2&gt;Internals and Security&lt;/h2&gt;
&lt;p&gt;To add support for login we &lt;a href=&quot;https://docusaurus.io/docs/swizzling#wrapper-your-site-with-root&quot;&gt;swizzled&lt;/a&gt; the &lt;code&gt;Root&lt;/code&gt; &lt;a href=&quot;https://docusaurus.io/docs/swizzling#wrapper-your-site-with-root&quot;&gt;component&lt;/a&gt; of Docusaurus. Docusaurus renders the Root component on the top of the React component tree making it the perfect candidate as a container for authentication state. When authentication is set to mandatory, our theme checks whether a user is authenticated before displaying the rest of the React tree.&lt;/p&gt;
&lt;p&gt;Our fork of the &lt;code&gt;docusaurus-openapi-docs&lt;/code&gt; theme will look for attributes names stored in the &lt;code&gt;persistentParamNames&lt;/code&gt; config of docusaurus and if those attributes are parameters or headers in the REST API it the theme will autofill them with the user attributes stored in Data Vault.&lt;/p&gt;
&lt;p&gt;While we do not recommend storing production API keys without adequate access control, our Data Vault module encrypts user data and attributes with a per-user, per-attribute key whose root of trust is derived from an HSM making the storage of any information in our database a safe choice.&lt;/p&gt;
&lt;h2&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;Using the &lt;code&gt;docusaurus-slashid-login&lt;/code&gt; with docusaurus-openapi-docs is a simple five steps process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Add the theme to your docusaurus project
&lt;code&gt;yarn add docusaurus-theme-openapi-docs-slashid docusaurus-theme-slashid docusaurus-plugin-openapi-docs-slashid&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure the openapi plugin as described in the &lt;a href=&quot;https://github.com/slashid/docusaurus-openapi-docs&quot;&gt;README&lt;/a&gt; of the docusaurus-openapi-docs repository&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a theme property to the &lt;code&gt;config&lt;/code&gt; object in &lt;code&gt;docusaurus.config.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;themes: [
    &quot;@slashid/docusaurus-theme-openapi-docs-slashid&quot;,
    &quot;@slashid/docusaurus-theme-slashid&quot;,
],
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a property to the &lt;code&gt;themeConfig&lt;/code&gt; object in &lt;code&gt;docusaurus.config.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;slashID: {
  orgID: YOUR_SLASHID_ORGANIZATION_ID
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a property to the &lt;code&gt;config&lt;/code&gt; in &lt;code&gt;docusaurus.config.js&lt;/code&gt; with the list of Data Vault attributes you&apos;d want to prefill in docusaurus:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;customFields: {
    persistentParamNames: [&apos;SlashID-OrgID&apos;]
},
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;h2&gt;Contributing and Feedback&lt;/h2&gt;
&lt;p&gt;We’d love to hear any feedback you might have on the project! While support for the theme is best-effort, we actively maintain it for our developer portal so we’d love to see it grow.&lt;/p&gt;
&lt;p&gt;To get started, &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=docusaurus-bp&quot;&gt;sign up&lt;/a&gt; for a free SlashID account and then follow the steps outlined in &lt;a href=&quot;https://developer.slashid.dev/docs/access/integrations/docusaurus-login&quot;&gt;the documentation&lt;/a&gt;.&lt;/p&gt;
</content:encoded><author>Ivan Kovic, Vincenzo Iozzo</author></item><item><title>Illicit Consent-Granting &amp; App Backdooring – Obtaining persistence in Entra</title><link>https://slashid.dev/blog/entra-app-backdooring/</link><guid isPermaLink="true">https://slashid.dev/blog/entra-app-backdooring/</guid><description>Attackers are increasingly targeting Entra ID by silently injecting high-privilege OAuth grants and backdooring enterprise apps—achieving persistence without user interaction.  This blog provides a technical deep dive into the full attack lifecycle - initial access, consent injection, privilege escalation, and evasion.  We map each stage to MITRE ATT&amp;CK, show real-world Graph API and CLI techniques, and outline concrete detection signals and hardening practices for defenders.</description><pubDate>Sun, 31 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Imagine a stealthy intruder slipping into your Azure Active Directory and quietly handing themselves the keys to the kingdom. That’s exactly what happens when attackers automate the injection of high-privilege OAuth consent grants into Entra ID (formerly Azure AD), backdoor existing applications, and then take full control of your entire tenant—all without a single click from an administrator.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How they do it&lt;/strong&gt;: Using a blend of Microsoft Graph API calls (targeting endpoints like &lt;code&gt;/oauth2PermissionGrants&lt;/code&gt; and &lt;code&gt;/applications/{id}&lt;/code&gt;), the Azure CLI, and custom scripts, these adversaries elevate their privileges programmatically and make their foothold permanent.&lt;/p&gt;
&lt;p&gt;This technique allows an attacker to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read and write virtually all directory data&lt;/li&gt;
&lt;li&gt;Spin up brand-new service principals under your radar&lt;/li&gt;
&lt;li&gt;Steal sensitive information at will&lt;/li&gt;
&lt;li&gt;And slip past normal revocation processes, keeping their backdoor open&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What you’ll find in this blogpost:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A clear attack flow diagram showing each step of the compromise&lt;/li&gt;
&lt;li&gt;In-depth technical breakdowns and real-world code snippets (HTTP requests, CLI commands, Python scripts)&lt;/li&gt;
&lt;li&gt;MITRE ATT&amp;amp;CK mappings to help you fit this campaign into your existing threat model&lt;/li&gt;
&lt;li&gt;Practical detection strategies and hardening steps to lock down your tenant before it’s too late&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’ll see exactly how the attack works — and how to stop it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Attack Flow&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/blog/entra-consent/attack-flow.png&quot; alt=&quot;Attack Flow Diagram&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Key lesson: Don’t let just anyone approve new applications or grant permissions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Lock down consent so only a handful of trusted admins can do it, turn on Privileged Identity Management (PIM) for consent-related roles, and keep a close eye on every call to &lt;code&gt;/oauth2PermissionGrants&lt;/code&gt; and every update to your app configurations.&lt;/p&gt;
&lt;h2&gt;MITRE ATT&amp;amp;CK Mapping&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;T1566&lt;/strong&gt; – Phishing for initial access&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T1078.004&lt;/strong&gt; – Hijacking valid cloud accounts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T1550.003&lt;/strong&gt; – Misusing OAuth tokens to stay under the radar&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T1134.003&lt;/strong&gt; – Manipulating access tokens for unauthorized privilege&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T1598.005&lt;/strong&gt; – Abusing app access-token mechanics to elevate control&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Attack Lifecycle&lt;/h2&gt;
&lt;h3&gt;Phase 1: Getting In&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tricking Users into Consent&lt;/strong&gt;: phishing email or OAuth pharming to grant rogue applications minimal but dangerous scopes like &lt;code&gt;OAuth2PermissionGrant.ReadWrite.All&lt;/code&gt;. Both of those scopes are enough to hand the attacker a token capable of rewriting consent grants or assigning new roles behind your back.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stealing Managed Identity Tokens via SSRF&lt;/strong&gt;: If they’ve already compromised a server in your cloud, they can trigger a server-side request forgery against the Azure Instance Metadata Service
(http://169.254.169.254/metadata/identity/oauth2/token). By coaxing that endpoint into revealing its managed identity token, the attacker gains powerful credentials without ever needing a password or an interactive grant.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/blog/entra-consent/lifecycle.png&quot; alt=&quot;SSRF&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Phase 2: Consent Injection&lt;/h3&gt;
&lt;p&gt;Now that the attacker holds a valid OAuth token, they quietly push a “blanket” consent grant across your entire tenant for their malicious application.&lt;/p&gt;
&lt;p&gt;By leveraging OAuth token manipulation (T1550.003) and access-token tricks (T1134.003), they assign delegated permissions that let the rogue app act on behalf of any user—without a single additional approval prompt.&lt;/p&gt;
&lt;p&gt;This tenant-wide &lt;code&gt;AllPrincipals&lt;/code&gt; consent effectively makes every user implicitly trust the attacker’s backdoor.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/entra-consent/mitre-attack.png&quot; alt=&quot;Consent Injection&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;What “AllPrincipals” really does&lt;/h4&gt;
&lt;p&gt;Imagine telling Azure, “I trust this application on behalf of everyone in our organization”—that’s exactly what an &lt;code&gt;AllPrincipals&lt;/code&gt; consent grant accomplishes.&lt;/p&gt;
&lt;p&gt;Instead of prompting each user or admin to approve permissions one by one, the malicious app gains delegated access across your entire tenant in one fell swoop.&lt;/p&gt;
&lt;h4&gt;What the attacker needs&lt;/h4&gt;
&lt;p&gt;Of course, they can’t just do this out of thin air.&lt;/p&gt;
&lt;p&gt;The caller (the compromised identity or service principal) must already possess the ability to create or modify consent grants—typically the
&lt;code&gt;OAuth2PermissionGrant.ReadWrite.All&lt;/code&gt; permission or a custom role with equivalent rights.&lt;/p&gt;
&lt;p&gt;In other words, they need full write access to your consent settings before they can inject that tenant-wide backdoor.&lt;/p&gt;
&lt;h3&gt;Phase 3: Privilege Escalation (T1598.005 ∙ T1134.001)&lt;/h3&gt;
&lt;p&gt;Tamper the application manifest to add app-only permissions like &lt;code&gt;Directory.ReadWrite.All&lt;/code&gt; and grant them. The malicious app now acts as a service principal.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/entra-consent/detection.png&quot; alt=&quot;App manifest tampering&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;What’s happening here&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Manifest tweak&lt;/strong&gt;: The first command injects a new entry under &lt;code&gt;requiredResourceAccess&lt;/code&gt; in the app’s manifest, asking Azure for the &lt;code&gt;Directory.ReadWrite.All&lt;/code&gt; app-only permission.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permission grant&lt;/strong&gt;: The second command gives that permission its “green light,” so the app can immediately request a client-credentials token with those rights.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With those two steps completed, the malicious application can now call the Graph API as itself—no user token needed—and wield full control over directory data, role assignments, service principals, and more. That’s the final leap from “sneaky user-level backdoor” to “unstoppable service principal attacker.”&lt;/p&gt;
&lt;h3&gt;Phase 4: Persistence &amp;amp; Evasion&lt;/h3&gt;
&lt;p&gt;Once the rogue app can act on its own, the attacker ensures it never loses access by adding—or regularly rotating—a long-lived client secret:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/entra-consent/mitigation.png&quot; alt=&quot;Persistence&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;What’s happening here&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Defining a perpetual secret&lt;/strong&gt;
The payload creates a passwordCredential named &lt;code&gt;PersistSecret&lt;/code&gt; with an end date set to the year 2299—practically forever.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Graph API injection&lt;/strong&gt;
By POSTing to &lt;code&gt;/applications/{app_id}/addPassword&lt;/code&gt; with their elevated token, the attacker plants this long-lived secret directly into the app’s configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permanent foothold&lt;/strong&gt;
Even if you revoke existing credentials or block the original service principal, this new secret remains valid—giving the malicious app permanent login capability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evasion &amp;amp; rotation&lt;/strong&gt;
They can rerun this script periodically (T1550.003) or swap out secrets on the fly (T1134.003), staying one step ahead of your revocation and monitoring efforts.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With this final move, the attacker cements an almost unassailable backdoor—complete with stealthy persistence and continuous access.&lt;/p&gt;
&lt;h3&gt;Phase 5: The Fallout&lt;/h3&gt;
&lt;p&gt;At this point, the attacker isn’t just hiding in the shadows—they’re reshaping your tenant to suit their goals, with consequences that reach every corner of your directory.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tenant-wide data exfiltration&lt;/strong&gt;
With permissions like &lt;code&gt;User.Read.All&lt;/code&gt;, &lt;code&gt;Group.Read.All&lt;/code&gt;, and even
&lt;code&gt;Directory.ReadWrite.All&lt;/code&gt;, the malicious app can quietly harvest virtually every piece of user and group information you’ve stored. Think contact lists, membership rosters, organizational charts—any data that helps them map your environment or impersonate insiders.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shadow administration&lt;/strong&gt;
By creating or tweaking service principals, spinning up rogue user accounts, or altering your conditional access policies, the attacker effectively builds their own hidden admin team inside your tenant. These “ghost” admins can maintain the compromise, pivot to new targets, or cover the tracks of more destructive actions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Long-term persistence&lt;/strong&gt;
Because the app lives on as a fully “trusted” enterprise application—with all the hallmarks of a legitimate service—it’s unlikely to raise red flags. Even routine audits or credential rotations may overlook it, giving the attacker a durable backdoor that survives user turnover, password resets, and policy changes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Taken together, these impacts turn a stealthy breach into a strategic stronghold—one that can siphon your data, manipulate your policies, and stay active for months or even years before detection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Spotting the Intruder&lt;/h2&gt;
&lt;p&gt;Detection strategies include:&lt;/p&gt;
&lt;h3&gt;Audit Log Alerts&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Watch for &lt;code&gt;Add servicePrincipalOAuth2PermissionGrant&lt;/code&gt; events—this is when new tenant-wide consents appear.&lt;/li&gt;
&lt;li&gt;Flag any &lt;code&gt;Update application&lt;/code&gt; operations, especially on manifests or permission sets.&lt;/li&gt;
&lt;li&gt;Catch &lt;code&gt;Add passwordCredential&lt;/code&gt; calls, which signal that a new client secret has been planted.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Hunt for “AllPrincipals”&lt;/h3&gt;
&lt;p&gt;Scan Graph audit records for consentType: &lt;code&gt;AllPrincipals&lt;/code&gt;. That blanket consent is your smoking gun.&lt;/p&gt;
&lt;h3&gt;Unusual Graph API Activity&lt;/h3&gt;
&lt;p&gt;Correlate high-privilege Graph calls (like modifying apps or grants) with identities that don’t normally handle admin tasks.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Prevention&lt;/h2&gt;
&lt;p&gt;Mitigation best practices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Limit OAuth Consent&lt;/strong&gt; In Azure AD → Enterprise Applications → Consent and Permissions, restrict consent approval to a small, trusted admin group.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Turn on PIM for Consent Roles&lt;/strong&gt; Require Privileged Identity Management (PIM) approval for roles like
&lt;code&gt;Application.ReadWrite.OwnedBy&lt;/code&gt; and &lt;code&gt;OAuth2PermissionGrant.ReadWrite.All.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Just-In-Time Approvals&lt;/strong&gt; Enforce JIT workflows on every high-impact role assignment so no one holds standing power indefinitely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated Audits&lt;/strong&gt; Schedule Graph-based audits for manifests and &lt;code&gt;/oauth2PermissionGrants&lt;/code&gt; - catch changes before they become permanent backdoors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By pairing vigilant monitoring with strict, time-bound controls, you’ll turn what was once an easy stealth attack into a high-risk, highly visible operation—one attackers are far less likely to attempt.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Illicit consent-granting and Entra app backdooring are among the most dangerous persistence techniques in the Azure identity threat landscape. By understanding the tactics—consent injection, manifest tampering, long-lived secrets—you can tighten controls and improve detection before adversaries exploit them.&lt;/p&gt;
&lt;p&gt;Identity is the new perimeter. Secure your consent flows before attackers do.&lt;/p&gt;
</content:encoded><author>SlashID Team, Vincenzo Iozzo</author></item><item><title>Firebase Authentication and Google Identity Platform User Enumeration Vulnerability</title><link>https://slashid.dev/blog/firebase-auth-vulnerability/</link><guid isPermaLink="true">https://slashid.dev/blog/firebase-auth-vulnerability/</guid><description>Firebase Authentication and Google Identity Platform are the two Google products that offer identity management.  Both products suffer from a vulnerability that allows an attacker to enumerate users on an application powered by Firebase Authentication or Google Identity Platform.  In this article, we’ll show how this vulnerability can be used and what the impact is.</description><pubDate>Mon, 27 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Update(12/12/2023): The Google security team reached out to say that they released an option to disable email enumeration on September 15th 2023 - more information &lt;a href=&quot;https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection&quot;&gt;here&lt;/a&gt;. We recommend enabling this option if you are using Google Identity Platform.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Firebase Authentication and its enterprise version, Google Identity Platform, are very popular solutions for adding identity to an application.&lt;/p&gt;
&lt;p&gt;During the course of a migration for a customer, we identified an issue in both platforms that can lead to the enumeration of users registered on an application.&lt;/p&gt;
&lt;p&gt;We contacted Google on September 7th 2023 through their vulnerability disclosure program, and we received an answer on October 31st from their Trust and Safety team telling us that
the issue won&apos;t be fixed, as it is behavior outlined in their &lt;a href=&quot;https://policies.google.com/privacy?hl=en-US#infosharing&quot;&gt;privacy policy&lt;/a&gt;. In particular, they stated that if an attacker has knowledge of the email address of a user, the intended behavior is for the attacker to be able to see information about that user.&lt;/p&gt;
&lt;p&gt;We disagree with Google&apos;s approach, as this kind of issue reduces user privacy and can lead to several downstream attacks. Further, several CVEs have been assigned to issues like this, and OWASP has a specific test for it in their Web Security Testing Guide, &lt;a href=&quot;https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/03-Identity_Management_Testing/04-Testing_for_Account_Enumeration_and_Guessable_User_Account&quot;&gt;WSTG-IDNT-04&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The issue is under embargo but can be found &lt;a href=&quot;https://issuetracker.google.com/issues/299478356?pli=1&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The details&lt;/h2&gt;
&lt;p&gt;Firebase Auth and Google Identity Platform have an &lt;a href=&quot;https://cloud.google.com/identity-platform/docs/reference/rest/v1/accounts/createAuthUri&quot;&gt;API endpoint&lt;/a&gt; that allows you to check whether a user is registered or not.&lt;/p&gt;
&lt;p&gt;The issue stems from the fact that you can use the API to enumerate accounts on a tenant. The API requires an API key to be used but, in practice, the API key is a misnomer for an identifier and it is not meant to be a secret. The API can be invoked directly from the frontend so anybody can read it from the website and reuse it.
Furthermore, Google does not enforce CORS, and the documentation does not clearly describe how the endpoint is supposed to be used, so we believe the behavior to be intentional.&lt;/p&gt;
&lt;p&gt;Reproducing this is straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Visit a website that uses Firebase Authentication or Google Identity Platform&lt;/li&gt;
&lt;li&gt;Find an invokation of &lt;code&gt;accounts:createAuthUri&lt;/code&gt; or any other Identity Platform API that requires an API Key&lt;/li&gt;
&lt;li&gt;Invoke the API, as shown below&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl &apos;https://identitytoolkit.googleapis.com/v1/accounts:createAuthUri?key=&amp;lt;API_KEY&amp;gt;&apos; --compressed -X POST -H &apos;Content-Type: application/json&apos; -H &apos;Origin: https://&amp;lt;DOMAIN&amp;gt;&apos; -H &apos;Pragma: no-cache&apos; -H &apos;Cache-Control: no-cache&apos; --data-raw &apos;{&quot;continueUri&quot;:&quot;&amp;lt;DOMAIN&amp;gt;&quot;,&quot;identifier&quot;:&quot;&amp;lt;EMAIL&amp;gt;&quot;}&apos;
{
  &quot;kind&quot;: &quot;identitytoolkit#CreateAuthUriResponse&quot;,
  &quot;allProviders&quot;: [
    &quot;password&quot;
  ],
  &quot;registered&quot;: true,
  &quot;sessionId&quot;: &quot;8fyGaPacaiHJPVVwPu32Alyxs-k&quot;,
  &quot;signinMethods&quot;: [
    &quot;password&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Severity&lt;/h2&gt;
&lt;p&gt;From our brief investigation it is not possible to find any other information about the user beyond what we show in the example above. Ultimately, whether this issue warrants attention depends on several factors, including:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What jurisdiction your users fall into and which data protection laws apply&lt;/li&gt;
&lt;li&gt;The kind of application and how sensitive is the knowledge that a given user exists on it (for example, healthcare applications may deem this sensitive)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Protecting against the issue&lt;/h2&gt;
&lt;p&gt;As discussed, Google will not fix this behavior, as it is intended according to their Privacy Policy. If you would like to avoid this issue the only recommendation at this time is to avoid using API keys in any frontend calls to Firebase/Google Identity Platform.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If you are interested in migrating from Firebase Authentication/Google Identity Platform to SlashID, you can get a free account &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=firebasebug-bp&quot;&gt;here&lt;/a&gt;
or &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;reach out to us&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>SlashID Team, Vincenzo Iozzo</author></item><item><title>Docusaurus - Authentication and authorization with SlashID</title><link>https://slashid.dev/blog/docusaurus-authentication-authorization/</link><guid isPermaLink="true">https://slashid.dev/blog/docusaurus-authentication-authorization/</guid><description>The latest docusaurus-slashid-login theme adds finer grained access control to your Docusaurus website.</description><pubDate>Mon, 28 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Interested in implementing access control for Docusaurus? Check out our integration guide &lt;a href=&quot;https://developer.slashid.dev/docs/access/integrations/docusaurus-login&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A few months ago, we wrote about &lt;a href=&quot;https://www.slashid.dev/blog/docusaurus-identity/&quot;&gt;adding identity to Docusaurus&lt;/a&gt; using the SlashID &lt;a href=&quot;https://www.npmjs.com/package/@slashid/docusaurus-theme-slashid&quot;&gt;Docusaurus theme&lt;/a&gt;. Today, we are proud to announce the new version of the theme, featuring more granular control, such as page- and path-based access control.
Keep reading to find out how, or skip straight to the &lt;a href=&quot;https://www.npmjs.com/package/@slashid/docusaurus-theme-slashid&quot;&gt;documentation&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;
&lt;video style=&quot;width:100%&quot; autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/blog/docusaurus-authentication-authorization/video.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&gt;&lt;/p&gt;
&lt;h2&gt;Page-level access control&lt;/h2&gt;
&lt;p&gt;The most fine-grained way of controlling access to your pages is by defining the access rules in the &lt;a href=&quot;https://docusaurus.io/docs/markdown-features#front-matter&quot;&gt;front matter&lt;/a&gt; of a given page. Here’s an example config:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sidebar_custom_props:
  slashid:
    auth: true
    groups:
      - member
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When defining access rules for a particular page, it&apos;s crucial to ensure that the page does not appear in the sidebar navigation if the current user should not have access. Docusaurus exposes the &lt;a href=&quot;https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-docs#sidebar_custom_props&quot;&gt;sidebar_custom_props&lt;/a&gt; front matter field, which we used to centralize page-level configuration.&lt;/p&gt;
&lt;p&gt;By specifying &lt;code&gt;auth: true&lt;/code&gt;, you will hide the page and the related sidebar item from the UI to unauthenticated users. To further refine access control rules, you can also specify a list of &lt;code&gt;groups&lt;/code&gt; the user needs to be a member of.&lt;/p&gt;
&lt;p&gt;Check the page level access control configuration in our &lt;a href=&quot;https://github.com/slashid/docusaurus-slashid-login/blob/main/apps/demo/docs/tutorial-extras/manage-docs-versions.md?plain=1&quot;&gt;demo app&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Path-based access control&lt;/h2&gt;
&lt;p&gt;Along with the page level access control, you can control access to an entire set of pages using the path-based configuration. A single configuration entry consisting of a glob or a RegExp can restrict access to all the pages matching the given pattern. This is specified in &lt;code&gt;docusaurus.config.js&lt;/code&gt; by adding a new field to your existing &lt;code&gt;slashID&lt;/code&gt; configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;​​      slashID: {
        orgID: &quot;your slash id org id&quot;,
        // …other configuration entries
        privatePaths: [
          {
            path: &quot;a glob or a regex specifying the path to protect&quot;,
            groups: [&quot;optional list of groups that can access the path&quot;],
          }
        ],
      },

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can specify multiple paths, each of them defined using a glob pattern or a RegExp. &lt;code&gt;groups&lt;/code&gt; are optional, and they work as described in the previous section: the authenticated user must belong to all the specified groups in order to access the page.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/slashid/docusaurus-slashid-login/blob/main/apps/demo/docusaurus.config.js#L76&quot;&gt;demo app&lt;/a&gt; contains an example of path-based access control.&lt;/p&gt;
&lt;h2&gt;Alternative approach&lt;/h2&gt;
&lt;p&gt;Docusaurus is a static site generator so your website will most probably be deployed on a CDN. With the above setup, access control is performed client side, meaning that the browser has to download the assets with the content, parse and execute them before determining if the page should be accessible to the current user. In other words, access control is only performed after downloading the assets. As a result, you are not protected against users deliberately downloading the files from the CDN and inspecting them for content.&lt;/p&gt;
&lt;p&gt;If your hosting provider supports edge functions (i.e functions that run at the edge of the network before serving files from the CDN) there is a way around the above problem – look out for our next blog post that explains exactly how to do that!&lt;/p&gt;
&lt;h2&gt;Contributing and Feedback&lt;/h2&gt;
&lt;p&gt;We’d love to hear any feedback you may have on the project! While support for the theme is best-effort, we actively maintain it for our developer portal so we’d love to see it grow. To get started, &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=docusaurus-bp&quot;&gt;sign up&lt;/a&gt; for a free SlashID account and then follow the steps outlined in &lt;a href=&quot;https://developer.slashid.dev/docs/access/integrations/docusaurus-login&quot;&gt;the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have any questions, please reach out to us directly or &lt;a href=&quot;https://github.com/slashid/docusaurus-slashid-login/issues&quot;&gt;open an issue&lt;/a&gt; on GitHub.&lt;/p&gt;
</content:encoded><author>Ivan Kovic, Vincenzo Iozzo</author></item><item><title>No-code anti-phishing protection of internal apps with Passkeys</title><link>https://slashid.dev/blog/gate-passkeys/</link><guid isPermaLink="true">https://slashid.dev/blog/gate-passkeys/</guid><description>Phishing is one of the most common causes of data breaches. According to Verizon&apos;s DBIR report, over 50% of incidents start with phishing or stolen credentials. WebAuthn/Passkeys are an effective way to stop phishing and credential stealing attempts on their tracks.  In this article, we’ll show how you can use Gate to enforce Passkeys authentication for users without modifying the application code.</description><pubDate>Mon, 18 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Phishing is among the most common causes of data breaches. Attackers often trick users into inputing their credentials into websites that look legitimate to the human-eye but
are actually setup by the attacker to steal credentials.&lt;/p&gt;
&lt;p&gt;While MFA is an effective tool to reduce this kind of attacks, it is unfortunately insufficient. Multiple campaigns in the past have shown how attackers can get around MFA through various techniques.&lt;/p&gt;
&lt;p&gt;WebAuthn/Passkeys provide strong protection against phishing attacks. In fact, all compliant browsers enforce a number of security checks that render phishing unfeasible in the majority of scenarioes.&lt;/p&gt;
&lt;p&gt;Enforcing access to apps through Passkeys is not always possible as the company might not have access to the source code or might not have the resources to dedicate to the project.
Gate is an effective way to easily enforce complex authentication and authorization policies out-of-the-box on web applications without any code changes.&lt;/p&gt;
&lt;p&gt;Read on to see an example on how to add fine-grained access control and Passkeys auth to an arbitrary application in 10 minutes or less.&lt;/p&gt;
&lt;h2&gt;WebAuthn and phishing&lt;/h2&gt;
&lt;p&gt;WebAuthn/Passkeys are public-private keypairs where only the public key is transmitted to the server, making credential stealing unfeasible unless an attacker compromises a user machine.&lt;/p&gt;
&lt;p&gt;Further, the WebAuthn spec and implementations in the browsers have been designed to be phishing-resistant and several checks are in place for an authentication ceremony to happen.&lt;/p&gt;
&lt;p&gt;First of all, WebAuthn doesn’t work without TLS. Furthermore, browsers enforce origin checks 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;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. This is the only way in which they can bypass the WebAuthn checks.&lt;/p&gt;
&lt;p&gt;In either scenario, a successful attack still won&apos;t give the attacker 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;Most importantly, due to the origin checks, most of the recent attacks involving domain squatting and phishing would fall flat because the reverse proxy wouldn’t be able to initiate the WebAuthn authentication process.&lt;/p&gt;
&lt;p&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 vastly superior authentication method.&lt;/p&gt;
&lt;h2&gt;The internal app: YouTrack&lt;/h2&gt;
&lt;p&gt;For this example we are going to assume that we have a self-hosted deployment of YouTrack, the popular issue tracker software.
For simplicity, we&apos;ll use docker compose for the deployment but Gate supports several deployment &lt;a href=&quot;https://developer.slashid.dev/docs/gate/deploying&quot;&gt;topologies&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3.3&apos;

services:
  youtrack:
    image: jetbrains/youtrack@sha256:&amp;lt;hash&amp;gt;
    container_name: &apos;youtrack&apos;
    volumes:
      - /mnt/disks/youtrack-data/youtrack/data:/opt/youtrack/data
      - /mnt/disks/youtrack-data/youtrack/conf:/opt/youtrack/conf
      - /mnt/disks/youtrack-data/youtrack/logs:/opt/youtrack/logs
      - /mnt/disks/youtrack-data/youtrack/backups:/opt/youtrack/backups
    expose:
      - &apos;8080&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Deploying Gate&lt;/h2&gt;
&lt;p&gt;We are going to deploy Gate in front of the application such that Gate can intercept unauthenticated requests and prompt the user to login.
Visually, it looks as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/auth-proxy/app-proxy.png&quot; alt=&quot;Configuration&quot; /&gt;&lt;/p&gt;
&lt;p&gt;What the Authentication Proxy plugin does is to intercept HTTP requests and if no valid authentication token is found, Gate injects a login page in front of the application.
The user logs in with one of the allowed login methods and is then redirected to the original app (in this case YouTrack).&lt;/p&gt;
&lt;p&gt;Once the user has authenticated the first time, future requests are passed to YouTrack transparently.&lt;/p&gt;
&lt;p&gt;Here&apos;s what the docker compose looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3.3&apos;

services:
  youtrack:
    image: jetbrains/youtrack@&amp;lt;hash&amp;gt;
    container_name: &apos;youtrack&apos;
    volumes:
      - /mnt/disks/youtrack-data/youtrack/data:/opt/youtrack/data
      - /mnt/disks/youtrack-data/youtrack/conf:/opt/youtrack/conf
      - /mnt/disks/youtrack-data/youtrack/logs:/opt/youtrack/logs
      - /mnt/disks/youtrack-data/youtrack/backups:/opt/youtrack/backups
    expose:
      - &apos;8080&apos;

  gate:
    container_name: gate
    image: slashid/gate:latest
    command: [&apos;-env&apos;]
    hostname: gate
    restart: unless-stopped
    environment:
      - GATE_PORT=80
      - GATE_DEFAULT_TARGET=http://youtrack:8080
      - GATE_LOG_FORMAT=text
      - GATE_LOG_LEVEL=info
      - GATE_HEADERS_FORWARDED_SET_OUTBOUND_X_FORWARDED=true
      - GATE_HEADERS_FORWARDED_PRESERVE_INBOUND_X_FORWARDED=true
      - GATE_PLUGINS_1_TYPE=authentication-proxy
      - GATE_PLUGINS_1_ENABLED=true
      - GATE_PLUGINS_1_PARAMETERS_LOGIN_PAGE_HEADING=Welcome to YouTrack
      - GATE_PLUGINS_1_PARAMETERS_STORE_LAST_HANDLE=true
    env_file:
      - .secret.env
    ports:
      - &apos;80:80&apos;
    depends_on:
      - youtrack
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Customizing the login page&lt;/h2&gt;
&lt;p&gt;The authentication proxy plugin supports a number of customization, as shown in the &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins/authentication-proxy&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In particular, it is possible to specify the valid authentication methods supported:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;login_page_factors:
  - method: &apos;webauthn&apos;
    options:
      &amp;lt;Factor option&amp;gt;: &apos;&amp;lt;Factor option value&amp;gt;&apos;
  - method: &apos;oidc&apos;
    options:
      &amp;lt;Another factor option&amp;gt;: &apos;&amp;lt;Another factor option value&amp;gt;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Creating complex authorization policies&lt;/h2&gt;
&lt;p&gt;The authentication proxy plugin also allows to out-of-the-box restrict access to users belonging to specific groups.
For example, we could restrict access to users belonging to the &lt;code&gt;developers&lt;/code&gt; group.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;required_groups: [&apos;developers&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But sometimes it might be necessary to enforce more complex policies. For instance, we might want to restrict access
to users that belong to certain google groups. To do so, we can combine the &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins/opa&quot;&gt;OPA plugin&lt;/a&gt;
with the Authentication Proxy plugin. Let&apos;s see how:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
    - id: authz_admin_oidc
      type: opa
      enabled: true
      parameters:
        &amp;lt;&amp;lt;: *slashid_config
        policy_decision_path: /authz/allow
        policy: |
          package authz

          import future.keywords.if
          default allow := false

          allow if claims.oidc_tokens[0].oidc_groups[_] == &quot;developers@example.com&quot;

          parseCookies(cookies) = cookie {
              parsed = split(cookies, &quot;;&quot;)

              cookie = { k: v |
              	  raw = parsed[_]

                  pair = split(raw, &quot;=&quot;)
                  k = trim(pair[0], &quot; &quot;)
                  v = trim(substring(raw, count(sprintf(&quot;%s=&quot;, [pair[0]])), count(raw)), &quot; &quot;)
              }
          }

          cookies := c if {
              v := input.request.http.headers.Cookie[0]
              c := parseCookies(v)
          }

          claims := payload if {
              [_, payload, _] := io.jwt.decode(bearer_token)
          }

          bearer_token := t if {
              t := cookies[&quot;SID-AuthnProxy-Token&quot;]
          }

    - id: auth_proxy
      type: authentication-proxy
      enabled: true
      parameters:
        &amp;lt;&amp;lt;: *slashid_config
        login_page_heading: &quot;Hello!&quot;
        jwt_expected_issuer: https://api.slashid.com
        jwks_url: https://api.slashid.com/.well-known/jwks.json

        login_page_factors:
          - method: &quot;oidc&quot;
            options:
              client_id: &quot;&amp;lt;google client id&amp;gt;&quot;
              provider: &quot;google&quot;
  urls:
    - pattern: &quot;*&quot;
      target: http://youtrack:8000
      plugins:
        auth_proxy:
          enabled: true
        authz_admin_oidc:
          enabled: true
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s break it down, the authentication proxy configuration specifies &lt;code&gt;oidc&lt;/code&gt; as the only valid authentication method. You can read more &lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/SSO/oauth_creds_googlegroups&quot;&gt;here&lt;/a&gt; on how to configure SlashID to retrieve Google groups as part of the authentication process with OIDC.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: you could do the same with AzureAD. Read here to learn &lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/SSO/oauth_creds_azure&quot;&gt;more&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;authz_admin_oidc&lt;/code&gt; instance of the OPA plugin enforces the check on the presence of Google groups as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Parse the cookies in the headers&lt;/li&gt;
&lt;li&gt;Extract the bearer token from the &lt;strong&gt;SID-AuthnProxy-Token&lt;/strong&gt; header&lt;/li&gt;
&lt;li&gt;Check if the &lt;strong&gt;oidc_groups&lt;/strong&gt; array in the token contains the &lt;strong&gt;developers@example.com&lt;/strong&gt; group&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We could, of course, further restrict the policy to, for instance, specific IP addresses or user agents. The Gate OPA plugin provides a rich &lt;code&gt;input&lt;/code&gt; object to the policy with significant information on the incoming &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins/opa#policy-input-object-example&quot;&gt;request&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Seeing it in action&lt;/h2&gt;
&lt;p&gt;&lt;video style=&quot;width:100%&quot; autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/blog/auth-proxy/recording.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&gt;
&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, we&apos;ve shown how we can easily add Passkeys and fine-grained authorization policies to an arbitrary web application in less than 10 minutes without modifying its source code.&lt;/p&gt;
&lt;p&gt;We’d love to hear any feedback you may have! Try out Gate with a &lt;a href=&quot;https://console.slashid.dev/signup/gate?utm_source=gatepasskeys-bp&quot;&gt;free account&lt;/a&gt;. If you&apos;d like to use the PII detection plugin, please contact us at &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;contact@slashid.dev&lt;/a&gt;.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Fetching Google Groups with SlashID SSO</title><link>https://slashid.dev/blog/google-groups/</link><guid isPermaLink="true">https://slashid.dev/blog/google-groups/</guid><description>Use SlashID to fetch Google Groups as part of a user authentication flow.</description><pubDate>Mon, 16 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&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=&quot;https://www.figma.com/blog/inside-figma-securing-internal-web-apps/&quot;&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=&quot;https://developer.slashid.dev/docs/gate&quot;&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;/p&gt;
&lt;pre&gt;&lt;code&gt;
google: {
  &quot;access_token&quot;: &quot;xIh3qoOXAFTaiOma2ZS6eCgP7rJF6iNDzleux+t69h8XktpsbHDKpiuwH2SwDO5/pSfwwn1+GeYZjxYxGbcJHAfiTztUfz3XqiejND4NasfuCGs550d0YSruxuC5yeKN9GUBbap1t/El7db23GlzjdJodp85ZFJsYsF9NTvP7ws4LkaX64s4VvlVWVT4fxOctFjO3oE1iR/oOBD1QCawvPdrLomQYFcXzN0R1UY9mIlDtUvsXh9cT+gAT9vXdjZ+Q9hR/b2QVR/l241AzmZ4VeIbVPQ=&quot;,
  &quot;client_id&quot;: &quot;32189023109-4332109.apps.googleusercontent.com&quot;,
  &quot;expires_at&quot;: 1673092551,
  &quot;id_token&quot;: &quot;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==&quot;,
  &quot;oidc_groups&quot;: [
    &quot;test-group@slashid.dev&quot;,
    &quot;sso-guide@slashid.dev&quot;,
  ],
  &quot;provider&quot;: &quot;google&quot;,
  &quot;refresh_token&quot;: &quot;&quot;
}
&lt;/code&gt;&lt;/pre&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=&quot;https://developer.slashid.dev/docs/access/guides/SSO/oauth_creds_googlegroups&quot;&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;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;type&quot;: &quot;service_account&quot;,
  &quot;project_id&quot;: &quot;your-project-id&quot;,
  &quot;private_key_id&quot;: &quot;8167c485286f057e06e4a9d17e99ab4913627dbd&quot;,
  &quot;private_key&quot;: &quot;-----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&quot;,
  &quot;client_email&quot;: &quot;groups-retrieval-guide@your-project-id.iam.gserviceaccount.com&quot;,
  &quot;client_id&quot;: &quot;104092906165806390865&quot;,
  &quot;auth_uri&quot;: &quot;https://accounts.google.com/o/oauth2/auth&quot;,
  &quot;token_uri&quot;: &quot;https://oauth2.googleapis.com/token&quot;,
  &quot;auth_provider_x509_cert_url&quot;: &quot;https://www.googleapis.com/oauth2/v1/certs&quot;,
  &quot;client_x509_cert_url&quot;: &quot;https://www.googleapis.com/robot/v1/metadata/x509/groups-retrieval-guide%40your-project-id.iam.gserviceaccount.com&quot;,
  &quot;super_user&quot;: &quot;user@slashid.dev&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that done, we can add the credentials to SlashID through our external credentials &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-organizations-config-external-credentials&quot;&gt;API&lt;/a&gt; as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X POST &apos;https://api.sandbox.slashid.com/organizations/config/external-credentials&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;YOUR ORG ID&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;YOUR API KEY&amp;gt;&apos; \
--data-raw &apos;{
  &quot;extcred_provider&quot;: &quot;google&quot;,
  &quot;extcred_type&quot;: &quot;json_credentials&quot;,
  &quot;extcred_label&quot;: &quot;group retrieval credential&quot;
  &quot;json_blob&quot;: {
    &quot;type&quot;: &quot;service_account&quot;,
    &quot;project_id&quot;: &quot;your-project-id&quot;,
    &quot;private_key_id&quot;: &quot;8167c485286f057e06e4a9d17e99ab4913627dbd&quot;,
    &quot;private_key&quot;: &quot;-----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&quot;,
    &quot;client_email&quot;: &quot;groups-retrieval-guide@your-project-id.iam.gserviceaccount.com&quot;,
    &quot;client_id&quot;: &quot;104092906165806390865&quot;,
    &quot;auth_uri&quot;: &quot;https://accounts.google.com/o/oauth2/auth&quot;,
    &quot;token_uri&quot;: &quot;https://oauth2.googleapis.com/token&quot;,
    &quot;auth_provider_x509_cert_url&quot;: &quot;https://www.googleapis.com/oauth2/v1/certs&quot;,
    &quot;client_x509_cert_url&quot;: &quot;https://www.googleapis.com/robot/v1/metadata/x509/groups-retrieval-guide%40your-project-id.iam.gserviceaccount.com&quot;,
    &quot;super_user&quot;: &quot;user@slashid.dev&quot;
  }
}&apos;
&lt;/code&gt;&lt;/pre&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=&quot;https://developer.slashid.dev/docs/access/guides/SSO/sso_5min/&quot;&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;/p&gt;
&lt;pre&gt;&lt;code&gt;curl --location --request POST &apos;https://api.sandbox.slashid.com/organizations/sso/oidc/provider-credentials&apos; \
--header &apos;SlashID-OrgID: &amp;lt;YOUR ORG ID&amp;gt;&apos; \
--header &apos;SlashID-API-Key: &amp;lt;YOUR API KEY&amp;gt;&apos; \
--header &apos;Content-Type: application/json&apos; \
--data-raw &apos;{
    &quot;client_id&quot;: &quot;&amp;lt;CLIENT ID&amp;gt;&quot;,
    &quot;client_secret&quot;: &quot;&amp;lt;CLIENT SECRET&amp;gt;&quot;,
    &quot;provider&quot;: &quot;google&quot;,
    &quot;label&quot;: &quot;A label for this set of credentials&quot;
    &quot;external_cred&quot;: &amp;lt;EXTERNAL_CRED_ID&amp;gt;
}
&apos;
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;let user = await sid.id(oid, null, {
  method: &apos;oidc&apos;,
  options: {
    client_id: clientId,
    provider: provider,
    ux_mode: uxMode,
    requires_groups: &apos;true&apos;,
  },
})
&lt;/code&gt;&lt;/pre&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;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Firewalling OpenAI APIs: Data loss prevention and identity access control</title><link>https://slashid.dev/blog/gate-dlp-openai/</link><guid isPermaLink="true">https://slashid.dev/blog/gate-dlp-openai/</guid><description>Large Language Models (LLMs) have taken the world by storm, and they are now used for many tasks by consumers and enterprises alike. However, the risk of accidentally disclosing sensitive data to the models is very high as the recent Samsung case shown.  In this article, we’ll show how you can use Gate to detect sensitive data in requests sent to the OpenAI APIs, as well as enforcing access control so that only users with certain roles can access the APIs.</description><pubDate>Thu, 14 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Due to the cost of LLMs APIs it is critical to enforce rate limit and granular access control on them.
Further, concerns over sensitive data leakage is leading more and more companies to restricting or entirely banning access to &lt;a href=&quot;https://www.businessinsider.com/chatgpt-companies-issued-bans-restrictions-openai-amazon-apple-2023-7&quot;&gt;LLMs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this blog post we are going to show how we can use SlashID to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Block or monitor requests to OpenAI APIs that contain sensitive sensitive data&lt;/li&gt;
&lt;li&gt;Enforce RBAC on OpenAI APIs and safely store the OpenAI API Key&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In a future article, we&apos;ll show how to enforce rate limiting and throttling.&lt;/p&gt;
&lt;h2&gt;OpenAI APIs&lt;/h2&gt;
&lt;p&gt;The OpenAI &lt;a href=&quot;https://platform.openai.com/docs/api-reference&quot;&gt;APIs&lt;/a&gt; allow applications to access models and other utilities that OpenAI exposes.&lt;/p&gt;
&lt;p&gt;For instance, you can easily generate a chat completion with a call like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl https://api.openai.com/v1/chat/completions \
  -H &quot;Content-Type: application/json&quot; \
  -H &quot;Authorization: Bearer $OPENAI_API_KEY&quot; \
  -d &apos;{
     &quot;model&quot;: &quot;gpt-3.5-turbo&quot;,
     &quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;Say this is a test!&quot;}],
     &quot;temperature&quot;: 0.7
   }&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Introducing Gate&lt;/h2&gt;
&lt;p&gt;At SlashID, we believe that security begins with Identity. &lt;a href=&quot;https://www.slashid.dev/products/gate&quot;&gt;Gate&lt;/a&gt; is our identity-aware edge authorizer 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) and sensitive data exposed through your APIs or workloads.&lt;/p&gt;
&lt;h2&gt;Deploying Gate&lt;/h2&gt;
&lt;p&gt;In this blog post we&apos;ll assume you deploy Gate as a standalone service and proxy OpenAI traffic through it. We are using docker compose
for this exercise:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3.7&apos;

services:
  gate:
    image: slashid/gate
    ports:
      - 8080:8080
    command: [--yaml, /gate.yaml]
    volumes:
      - ./gate.yaml:/gate.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the most basic form, let&apos;s simply proxy a request to the OpenAI API and log it. This is what the &lt;code&gt;gate.yaml&lt;/code&gt; configuration file looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gate:
  port: &apos;8080&apos;
  log:
    format: text
    level: debug

  urls:
    - pattern: &apos;*/v1/chat/completions*&apos;
      target: https://api.openai.com/
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;curl https://gate:8080/v1/chat/completions
{
    &quot;error&quot;: {
        &quot;message&quot;: &quot;You didn&apos;t provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you&apos;re accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.&quot;,
        &quot;type&quot;: &quot;invalid_request_error&quot;,
        &quot;param&quot;: null,
        &quot;code&quot;: null
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Block requests containing PII data&lt;/h2&gt;
&lt;p&gt;We can use the Gate PII detection &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins/pii-detection&quot;&gt;plugin&lt;/a&gt; together with the OPA &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins/opa&quot;&gt;plugin&lt;/a&gt; to block requests to OpenAI APIs 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 configuration, but they can also be served through a bundle.&lt;/p&gt;
&lt;p&gt;Please check out our documentation to learn more about the plugin.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;gate:
  plugins:
    - id: pii_anonymizer
      type: anonymizer
      enabled: false
      parameters:
        anonymizers: |
          DEFAULT:
            type: keep
    - id: authz_deny_pii
      type: opa
      enabled: false
      intercept: request
      parameters:
        policy_decision_path: /authz/allow
        policy: |
          package authz

          import future.keywords.if

          default allow := false

          key_found(obj, key) if { obj[key] }

          allow if not key_found(input.request.http.headers, &quot;X-Gate-Anonymize-1&quot;)
---
urls:
  - pattern: &apos;*/v1/chat/completions*&apos;
    target: https://api.openai.com/
    plugins:
      pii_anonymizer:
        enabled: true
      authz_deny_pii:
        enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The PII Detection plugin is able to modify, hash, strip or simply detect PII data in requests/responses. In this example it is configured to only monitor for the presence of PII in requests:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;anonymizers: |
  DEFAULT:
  type: keep
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If any PII is detected, the PII plugin adds a series of headers to the request/response, &lt;code&gt;X-Gate-Anonymize-N&lt;/code&gt;, where &lt;code&gt;N&lt;/code&gt; is the number of PII detected.&lt;/p&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;.&lt;/p&gt;
&lt;p&gt;In other words, it blocks the request if the PII detection plugin found at least one PII element in the request.&lt;/p&gt;
&lt;p&gt;Let&apos;s see an example in action:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -vL https://gate:8080/v1/chat/completions \
  -H &quot;Content-Type: application/json&quot; \
  -d &apos;{
     &quot;model&quot;: &quot;gpt-3.5-turbo&quot;,
     &quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;email@email.com&quot;}],
     &quot;temperature&quot;: 0.7
   }&apos;

...
 POST /v1/chat/completions HTTP/1.1
&amp;gt; Host: gate:8080
&amp;gt; User-Agent: curl/7.87.0
&amp;gt; Accept: */*
&amp;gt; Content-Type: application/json
&amp;gt; Content-Length: 128
&amp;gt;
* Mark bundle as not supporting multiuse
&amp;lt; HTTP/1.1 403 Forbidden
&amp;lt; Via: 1.0 gate
&amp;lt; Date: Thu, 14 Sep 2023 01:57:40 GMT
&amp;lt; Content-Length: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Blocking other sensitive data&lt;/h2&gt;
&lt;p&gt;The PII detection plugin supports a number of PII selectors out of the box, but it is not limited to them. It is also possible to define custom categories of data
for the plugin to detect.&lt;/p&gt;
&lt;p&gt;In the example below we specify a pattern for data that either contains the word confidential or mentions &lt;code&gt;Acme Corp&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;analyzer_ad_hoc_recognizers: |
  - name: Acme Corp regex
  supported_language: en
  supported_entity: ACME_SENSITIVE_DATA
  patterns:
      - name: acme_data
          regex: &quot;*Acme Corp*&quot;
      - name: deny_list
          deny_list: &quot;confidential&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once we have specified the pattern we can see it in action like we did in the previous section:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -vL https://gate:8080/v1/chat/completions \
  -H &quot;Content-Type: application/json&quot; \
  -d &apos;{
     &quot;model&quot;: &quot;gpt-3.5-turbo&quot;,
     &quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;This data is confidential&quot;}],
     &quot;temperature&quot;: 0.7
   }&apos;

...
 POST /v1/chat/completions HTTP/1.1
&amp;gt; Host: gate:8080
&amp;gt; User-Agent: curl/7.87.0
&amp;gt; Accept: */*
&amp;gt; Content-Type: application/json
&amp;gt; Content-Length: 128
&amp;gt;
* Mark bundle as not supporting multiuse
&amp;lt; HTTP/1.1 403 Forbidden
&amp;lt; Via: 1.0 gate
&amp;lt; Date: Thu, 14 Sep 2023 01:57:40 GMT
&amp;lt; Content-Length: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Running in monitoring mode&lt;/h2&gt;
&lt;p&gt;In large, complex environments, it is not always possible or advisable to block requests. Gate allows to run a number of plugins in monitoring mode, where an alert is triggered but
the requests are not modified.&lt;/p&gt;
&lt;p&gt;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;/p&gt;
&lt;pre&gt;&lt;code&gt;    - id: authz_allow_if_authed_pii
      type: opa
      enabled: false
      intercept: request
      parameters:
        &amp;lt;&amp;lt;: *slashid_config
        monitoring_mode: true
        policy_decision_path: /authz/allow
        policy: |
          package authz

          import future.keywords.if

          default allow := false

          key_found(obj, key) if { obj[key] }

          allow if not key_found(input.request.http.headers, &quot;X-Gate-Anonymize-1&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s send a request with PII data:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -vL https://gate:8080/v1/chat/completions \
  -H &quot;Content-Type: application/json&quot; \
  -d &apos;{
     &quot;model&quot;: &quot;gpt-3.5-turbo&quot;,
     &quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;email@email.com&quot;}],
     &quot;temperature&quot;: 0.7
   }&apos;

{
    &quot;error&quot;: {
        &quot;message&quot;: &quot;You didn&apos;t provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you&apos;re accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.&quot;,
        &quot;type&quot;: &quot;invalid_request_error&quot;,
        &quot;param&quot;: null,
        &quot;code&quot;: null
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The request passes but Gate logs the policy violation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gate-llm-demo-gate-1        | time=2023-09-14T01:57:40Z level=info msg=OPA decision: false decision_id=9f248489-4876-40ed-824e-17fb35306c3b decision_provenance={0.55.0 19fc439d01c8d667b128606390ad2cb9ded04b16-dirty 2023-09-02T15:18:29Z   map[gate:{}]} plugin=opa req_path=/v1/chat/completions request_host=gate:8080 request_url=/v1/chat/completions
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Block requests from users with unauthorized roles&lt;/h2&gt;
&lt;p&gt;With Gate we can also block requests to the OpenAI APIs that are unauthenticated or come from users who don&apos;t belong to a specific group.
In this example we&apos;ll perform two actions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use the JWT token validator plugin to validate an authorization header minted by SlashID. We further check that the user belongs to the &lt;code&gt;openai_users&lt;/code&gt; group&lt;/li&gt;
&lt;li&gt;We use the token reminter plugin coupled with SlashID Data Vault storage module to forward the request to the OpenAI APIs swapping the SlashID Authorization header for the OpenAI API key&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We first define a Python webhook to remint the token and swap the Authorization header:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MintTokenRequest(BaseModel):
    slashid_token: str = Field(
        title=&quot;SlashID Token&quot;, example=&quot;eyJhbGciOiJIUzI1NiIsInR5cCI6Ik...&quot;
    )
    person_id: str = Field(
        title=&quot;SlashID Person ID&quot;, example=&quot;cc006198-ef43-42ac-9b5a-b52713569d0f&quot;
    )
    handles: Optional[List[Handle]] = Field(title=&quot;Person&apos;s login handles&quot;)

class MintTokenResponse(BaseModel):
    headers_to_set: Optional[Dict[str, str]]
    cookies_to_add: Optional[Dict[str, str]]

def retrieve_openai_key(person_id):
    # Construct the URL
    url = f&quot;https://api.slashid.com/persons/{person_id}/attributes/end_user_no_access&quot;

    # Perform the GET request
    response = requests.get(url)

    # Check if the request was successful
    if response.status_code == 200:
        data = response.json()
        return data[&apos;result&apos;][&apos;openai_apikey&apos;]
    else:
        print(f&quot;Request failed with status code {response.status_code}.&quot;)
        return None

@app.post(
    &quot;/remint_token&quot;,
    tags=[&quot;gate&quot;],
    status_code=status.HTTP_201_CREATED,
    summary=&quot;Endpoint called by Gate to translate SlashID token in a custom token&quot;,
)
def mint_token(request: MintTokenRequest) -&amp;gt; MintTokenResponse:
    logger.info(f&quot;/mint_token: request={request}&quot;)

    sid_token = request.slashid_token
    try:
        token = jwt.decode(sid_token, options={&quot;verify_signature&quot;: False})
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=f&quot;Token {sid_token} is invalid: {e}&quot;,
        )

    openai_token = retrieve_openai_key(token.get(&quot;person_id&quot;))

    if openai_token is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=f&quot;Unable to get openai token&quot;,
        )

    return MintTokenResponse(
        headers_to_set={&quot;Authorization&quot;: &quot;Bearer &quot; + openai_token}, cookies_to_add=None
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;mint_token&lt;/code&gt; function decodes the JWT token with the SlashID user, retrieves one of its Data Vault &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/attribute_buckets&quot;&gt;buckets&lt;/a&gt; where the OpenAI API key is stored and rewrite the Authorization header with the OpenAI key.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: The reminter works without SlashID. You could for instance look up the OpenAI API Key from your instance of Hashicorp Vault or from another location&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&apos;s now take a look at the Gate configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gate:
  plugins:
    - id: validator
      type: validate-jwt
      enable_http_caching: true
      enabled: false
      parameters:
        required_groups: [&apos;openai_users&apos;]
        jwks_url: https://api.slashid.com/.well-known/jwks.json
        jwks_refresh_interval: 15m
        jwt_expected_issuer: https://api.slashid.com
    - id: pii_anonymizer
      type: anonymizer
      enabled: true
      parameters:
        anonymizers: |
          DEFAULT:
            type: keep
    - id: authz_deny_pii
      type: opa
      enabled: false
      intercept: request
      parameters:
        policy_decision_path: /authz/allow
        policy: |
          package authz

          import future.keywords.if

          default allow := false

          key_found(obj, key) if { obj[key] }

          allow if not key_found(input.request.http.headers, &quot;X-Gate-Anonymize-1&quot;)
    - id: reminter
      type: token-reminting
      enabled: false
      parameters:
        remint_token_endpoint: http://backend:8000/remint_token

  port: &apos;8080&apos;
  log:
    format: text
    level: debug

  urls:
    - pattern: &apos;*/v1/chat/completions*&apos;
      target: https://api.openai.com/
      plugins:
        validator:
          enabled: true
        pii_anonymizer:
          enabled: true
        authz_deny_pii:
          enabled: true
        reminter:
          enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A lot of things are happening on the &lt;code&gt;*/v1/chat/completions*&lt;/code&gt; endpoint:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We first validate the Authorization header, including checking that the user belongs to the correct group, through the
&lt;code&gt;validator&lt;/code&gt; instance of the JWT Validator plugin.&lt;/li&gt;
&lt;li&gt;We then proceed to check whether the request contains any PII data with &lt;code&gt;pii_anonymizer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Next, we block the request through the OPA plugin &lt;code&gt;authz_deny_pii&lt;/code&gt; if any PII was detected.&lt;/li&gt;
&lt;li&gt;Finally, if the request made it this far we use the &lt;code&gt;reminter&lt;/code&gt; to swap the SlashID authorization token for the OpenAI API key and we let the request through.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In particular, the JWT validator checks the validity of the JWT token as well as whether the user belongs to the &lt;code&gt;openai_users&lt;/code&gt; group as shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- id: validator
  type: validate-jwt
  enable_http_caching: true
  enabled: false
  parameters:
    required_groups: [&apos;openai_users&apos;]
    jwks_url: https://api.slashid.com/.well-known/jwks.json
    jwks_refresh_interval: 15m
    jwt_expected_issuer: https://api.slashid.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s see it in action:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -vL https://gate:8080/v1/chat/completions \
  -H &quot;Content-Type: application/json&quot; \
  -H &quot;Authorization: Bearer abc&quot;
  -d &apos;{
     &quot;model&quot;: &quot;gpt-3.5-turbo&quot;,
     &quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;Say this is a test with SlashID!&quot;}],
     &quot;temperature&quot;: 0.7
   }&apos;

{
    &quot;id&quot;: &quot;chatcmpl-abc123&quot;,
    &quot;object&quot;: &quot;chat.completion&quot;,
    &quot;created&quot;: 1677858242,
    &quot;model&quot;: &quot;gpt-3.5-turbo-0613&quot;,
    &quot;usage&quot;: {
        &quot;prompt_tokens&quot;: 13,
        &quot;completion_tokens&quot;: 7,
        &quot;total_tokens&quot;: 20
    },
    &quot;choices&quot;: [
        {
            &quot;message&quot;: {
                &quot;role&quot;: &quot;assistant&quot;,
                &quot;content&quot;: &quot;\n\nThis is a test with SlashID!&quot;
            },
            &quot;finish_reason&quot;: &quot;stop&quot;,
            &quot;index&quot;: 0
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this brief blog post we&apos;ve demonstrated a few of the things you can do to securely access LLMs without compromising on privacy and security. Stay tuned for the next blog post
on rate limiting and OpenAI APIs.&lt;/p&gt;
&lt;p&gt;We’d love to hear any feedback you may have! Try out Gate with a &lt;a href=&quot;https://console.slashid.dev/signup/gate?utm_source=dplopenai-bp&quot;&gt;free account&lt;/a&gt;. If you&apos;d like to use the PII detection plugin, please contact us at &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;contact@slashid.dev&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Protecting Exposed APIs: Avoid Data Leaks with SlashID Gate and OPA</title><link>https://slashid.dev/blog/gate-dlp-api/</link><guid isPermaLink="true">https://slashid.dev/blog/gate-dlp-api/</guid><description>Adequately protecting APIs is key to avoid data leaks and breaches.  Just recently, an exposed API allowed an attacker to scrape over 2.6 million records from Duolingo.  In this article, we’ll show how you can use Gate to detect, respond to, and prevent these kinds of incidents.</description><pubDate>Tue, 05 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&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=&quot;https://twitter.com/IvanoSomaini/status/1631168225399525376?t=4xIAYDiwSenj1WUb5NjFcg&amp;amp;s=03&quot;&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 https://www.duolingo.com/2017-06-30/users?email endpoint reveals several private details about users and allows attackers to enumerate registered emails. Below an example output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;users&quot;: [
    {
      &quot;joinedClassroomIds&quot;: [],
      &quot;streak&quot;: 0,
      &quot;motivation&quot;: &quot;none&quot;,
      &quot;acquisitionSurveyReason&quot;: &quot;none&quot;,
      &quot;shouldForceConnectPhoneNumber&quot;: false,
      &quot;picture&quot;: &quot;//simg-ssl.duolingo.com/avatar/default_2&quot;,
      &quot;learningLanguage&quot;: &quot;ru&quot;,
      &quot;hasFacebookId&quot;: false,
      &quot;shakeToReportEnabled&quot;: null,
      &quot;liveOpsFeatures&quot;: [
        {
          &quot;startTimestamp&quot;: 1693007940,
          &quot;type&quot;: &quot;TIMED_PRACTICE&quot;,
          &quot;endTimestamp&quot;: 1693180740
        }
      ],
      &quot;canUseModerationTools&quot;: false,
      &quot;id&quot;: 184078602543312,
      &quot;betaStatus&quot;: &quot;INELIGIBLE&quot;,
      &quot;hasGoogleId&quot;: false,
      &quot;privacySettings&quot;: [],
      &quot;fromLanguage&quot;: &quot;en&quot;,
      &quot;hasRecentActivity15&quot;: false,
      &quot;_achievements&quot;: [],
      &quot;observedClassroomIds&quot;: [],
      &quot;username&quot;: &quot;example&quot;,
      &quot;bio&quot;: &quot;&quot;,
      &quot;profileCountry&quot;: &quot;US&quot;,
      &quot;chinaUserModerationRecords&quot;: [],
      &quot;globalAmbassadorStatus&quot;: {},
      &quot;currentCourseId&quot;: &quot;DUOLINGO_RU_EN&quot;,
      &quot;hasPhoneNumber&quot;: false,
      &quot;creationDate&quot;: 146229322008,
      &quot;achievements&quot;: [],
      &quot;hasPlus&quot;: false,
      &quot;name&quot;: &quot;o&quot;,
      &quot;roles&quot;: [&quot;users&quot;],
      &quot;classroomLeaderboardsEnabled&quot;: false,
      &quot;emailVerified&quot;: false,
      &quot;courses&quot;: [
        {
          &quot;preload&quot;: false,
          &quot;placementTestAvailable&quot;: false,
          &quot;authorId&quot;: &quot;duolingo&quot;,
          &quot;title&quot;: &quot;Russian&quot;,
          &quot;learningLanguage&quot;: &quot;ru&quot;,
          &quot;xp&quot;: 370,
          &quot;healthEnabled&quot;: true,
          &quot;fromLanguage&quot;: &quot;en&quot;,
          &quot;crowns&quot;: 7,
          &quot;id&quot;: &quot;DUOLINGO_RU_EN&quot;
        }
      ],
      &quot;totalXp&quot;: 370,
      &quot;streakData&quot;: {
        &quot;currentStreak&quot;: null
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&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 &quot;Add Friend&quot; 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. &lt;a href=&quot;https://www.slashid.dev/products/gate&quot;&gt;Gate&lt;/a&gt; is our identity-aware edge authorizer 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=&quot;https://developer.slashid.dev/docs/gate/getting-started/configuration&quot;&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;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3.7&apos;

services:
  backend:
    build: backend
    ports:
      - 8000:8000
    environment:
      - PORT=8000
    env_file:
      - envs/env.env
    restart: on-failure

  gate:
    image: slashid/gate:latest
    volumes:
      - ./gate.yaml:/gate/gate.yaml
    ports:
      - 8080:8080
    env_file:
      - envs/env.env
    command: --yaml /gate/gate.yaml
    restart: on-failure
    depends_on:
      - backend
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;users = [
    {&apos;email&apos;: &apos;test@example.com&apos;, &apos;name&apos;: &apos;Test User&apos;, &apos;id&apos;: 1},
    {&apos;email&apos;: &apos;john@example.com&apos;, &apos;name&apos;: &apos;John Doe&apos;, &apos;id&apos;: 2},
    # ... add more users if needed
]

def get_user_by_email(email: str) -&amp;gt; Optional[dict]:
    for user in users:
        if user[&apos;email&apos;] == email:
            return user
    return None

@app.get(&quot;/get_user/&quot;, tags=[&quot;business&quot;])
async def read_user(email: str = Query(..., description=&quot;The email of the user to search for&quot;)):
    user = get_user_by_email(email)
    if user:
        return user
    else:
        raise HTTPException(status_code=404, detail=&quot;User not found&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s test it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl &apos;http://gate:8080/get_user/?email=test@example.com&apos; | jq
{
  &quot;email&quot;: &quot;test@example.com&quot;,
  &quot;name&quot;: &quot;Test User&quot;,
  &quot;id&quot;: 1
}
&lt;/code&gt;&lt;/pre&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&apos;ll show an example in the next section.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&apos;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;/p&gt;
&lt;pre&gt;&lt;code&gt;gate:
  port: 8080
  log:
    format: text
    level: info

  plugins_http_cache:
    - pattern: &apos;*&apos;
      cache_control_override: private, max-age=600, stale-while-revalidate=300

  plugins:
    - id: pii_anonymizer
      type: anonymizer
      enabled: false
      intercept: request_response
      parameters:
        anonymizers: |
          EMAIL_ADDRESS:
            type: hash

  urls:
    - pattern: &apos;*/get_user&apos;
      target: http://backend:8000
      plugins:
        pii_anonymizer:
          enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s test it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl &apos;http://gate:8080/api/get_user/?email=test@example.com&apos; | jq
{
  &quot;email&quot;: &quot;973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b&quot;,
  &quot;id&quot;: 1,
  &quot;name&quot;: &quot;Test User&quot;
}
&lt;/code&gt;&lt;/pre&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&apos;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;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;gate:
  port: 8080
  log:
    format: text
    level: info

  plugins_http_cache:
    - pattern: &apos;*&apos;
      cache_control_override: private, max-age=600, stale-while-revalidate=300

  plugins:
    - id: authz_deny_pii
      type: opa
      enabled: false
      intercept: response
      parameters:
        &amp;lt;&amp;lt;: *slashid_config
        policy_decision_path: /authz/allow
        policy: |
          package authz

          import future.keywords.if

          default allow := false

          no_key_found(obj, key) {
            not obj[key]
          }

          allow if no_key_found(input.response.http.headers,  &quot;X-Gate-Anonymize-1&quot;)

    - id: pii_anonymizer
      type: anonymizer
      enabled: false
      intercept: request_response
      parameters:
        anonymizers: |
          DEFAULT:
            type: keep
  urls:
    - pattern: &apos;*/get_user&apos;
      target: http://backend:8000
      plugins:
        pii_anonymizer:
          enabled: true
        authz_deny_pii:
          enabled: true
&lt;/code&gt;&lt;/pre&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&apos;s see it in action:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/server/app $ curl --verbose &apos;http://gate:8080/api/get_user/?email=test@example.com&apos; | jq
* processing: http://gate:8080/api/get_user/?email=test@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 --:--:-- --:--:-- --:--:--     0*   Trying 172.27.0.5:8080...
* Connected to gate (172.27.0.5) port 8080
&amp;gt; GET /api/get_user/?email=test@example.com HTTP/1.1
&amp;gt; Host: gate:8080
&amp;gt; User-Agent: curl/8.2.1
&amp;gt; Accept: */*
&amp;gt;
&amp;lt; HTTP/1.1 403 Forbidden
&amp;lt; Cache-Control: private, max-age=600, stale-while-revalidate=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: $.body.email 0 64 EMAIL_ADDRESS
&amp;lt;
  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0
* Connection #0 to host gate left intact
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;- id: pii_anonymizer
  type: anonymizer
  enabled: false
  intercept: request_response
  parameters:
    anonymizers: |
      DEFAULT:
        type: keep
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Differential policy enforcement for authenticated users&lt;/h2&gt;
&lt;p&gt;Let&apos;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&apos;ll use SlashID Access to handle authentication, but any Identity Provider (IdP) would be suitable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;gate:
  port: 8080
  log:
    format: text
    level: info

  plugins_http_cache:
    - pattern: &apos;*&apos;
      cache_control_override: private, max-age=600, stale-while-revalidate=300

  plugins:
    - id: authz_allow_if_authed_pii
      type: opa
      enabled: false
      intercept: response
      parameters:
        &amp;lt;&amp;lt;: *slashid_config
        policy_decision_path: /authz/allow
        policy: |
          package authz

          import future.keywords.if

          default allow := false

          key_found(obj, key) if { obj[key] }

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

          allow if not key_found(input.response.http.headers, &quot;X-Gate-Anonymize-1&quot;)
          allow if valid_signature

    - id: pii_anonymizer
      type: anonymizer
      enabled: false
      intercept: request_response
      parameters:
        anonymizers: |

          DEFAULT:
            type: keep
  urls:
    - pattern: &apos;*/get_user&apos;
      target: http://backend:8000
      plugins:
        pii_anonymizer:
          enabled: true
        authz_deny_pii:
          enabled: true
&lt;/code&gt;&lt;/pre&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&apos;s see this in action:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl --verbose -L &apos;http://gate:8080/api/get_user/?email=test@example.com&apos; | jq
* processing: http://gate:8080/api/get_user/?email=test@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 --:--:-- --:--:-- --:--:--     0*   Trying 172.27.0.5:8080...
* Connected to gate (172.27.0.5) port 8080
&amp;gt; GET /api/get_user/?email=test@example.com HTTP/1.1
&amp;gt; Host: gate:8080
&amp;gt; User-Agent: curl/8.2.1
&amp;gt; Accept: */*
&amp;gt;
&amp;lt; HTTP/1.1 403 Forbidden
&amp;lt; Cache-Control: private, max-age=600, stale-while-revalidate=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: $.body.email 0 64 EMAIL_ADDRESS
&amp;lt;
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
* Connection #0 to host gate left intact
&lt;/code&gt;&lt;/pre&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&apos;s send a request with a valid token:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -H &quot;Authorization: Bearer &amp;lt;TOKEN&amp;gt;&quot; &apos;http://gate:8080/api/get_user/?email=test@example.com&apos; | jq
{
  &quot;email&quot;: &quot;test@example.com&quot;,
  &quot;id&quot;: 1,
  &quot;name&quot;: &quot;Test User&quot;
}
&lt;/code&gt;&lt;/pre&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.
(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&apos;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;/p&gt;
&lt;pre&gt;&lt;code&gt;
gate:
  port: 8080
  log:
    format: text
    level: info

  plugins_http_cache:
    - pattern: &quot;*&quot;
      cache_control_override: private, max-age=600, stale-while-revalidate=300

  plugins:
    - id: authz_allow_if_authed_pii
      type: opa
      enabled: true
      intercept: response
      parameters:
        &amp;lt;&amp;lt;: *slashid_config
        policy_decision_path: /authz/allow
        policy: |
          package authz

          import future.keywords.if

          default allow := false

          key_found(obj, key) if { obj[key] }

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

          allow if not key_found(input.response.http.headers, &quot;X-Gate-Anonymize-1&quot;)
          allow if valid_signature
    - id: pii_anonymizer
      type: anonymizer
      enabled: true
      intercept: request_response
      parameters:
        anonymizers: |
          DEFAULT:
            type: keep

  urls:

    - pattern: &quot;*/api/echo&quot;
      target: http://backend:8000
      plugins:
        authz_allow_if_authed_pii:
          enabled: false
        pii_anonymizer:
          enabled: false

    - pattern: &quot;*&quot;
      target: http://backend:8000

&lt;/code&gt;&lt;/pre&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;&quot;*/api/echo&quot;&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/server/app $ curl --verbose -X POST &apos;http://gate:8080/api/echo&apos; -d &quot;email=abc@abc.com&quot; | jq
Note: Unnecessary use of -X or --request, POST is already inferred.
* 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 --:--:-- --:--:-- --:--:--     0*   Trying 172.27.0.5:8080...
* Connected to gate (172.27.0.5) port 8080
&amp;gt; POST /api/echo HTTP/1.1
&amp;gt; Host: gate:8080
&amp;gt; User-Agent: curl/8.2.1
&amp;gt; Accept: */*
&amp;gt; Content-Length: 17
&amp;gt; Content-Type: application/x-www-form-urlencoded
&amp;gt;
} [17 bytes data]
&amp;lt; HTTP/1.1 200 OK
&amp;lt; Cache-Control: private, max-age=600, stale-while-revalidate=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;
{ [360 bytes data]
100   377  100   360  100    17  32933   1555 --:--:-- --:--:-- --:--:-- 37700
* Connection #0 to host gate left intact
{
  &quot;method&quot;: &quot;POST&quot;,
  &quot;headers&quot;: {
    &quot;host&quot;: &quot;backend:8000&quot;,
    &quot;user-agent&quot;: &quot;curl/8.2.1&quot;,
    &quot;content-length&quot;: &quot;17&quot;,
    &quot;accept&quot;: &quot;*/*&quot;,
    &quot;content-type&quot;: &quot;application/x-www-form-urlencoded&quot;,
    &quot;x-b3-sampled&quot;: &quot;1&quot;,
    &quot;x-b3-spanid&quot;: &quot;39b9a26c103c6b5d&quot;,
    &quot;x-b3-traceid&quot;: &quot;ce0b56fc209ec47fbe0496606595c06b&quot;,
    &quot;accept-encoding&quot;: &quot;gzip&quot;
  },
  &quot;url&quot;: &quot;http://backend:8000/api/echo&quot;,
  &quot;body&quot;: {
    &quot;email&quot;: &quot;abc@abc.com&quot;
  }
}
/usr/server/app $ curl --verbose &apos;http://gate:8080/api/get_user/?email=test@example.com&apos; | jq
* processing: http://gate:8080/api/get_user/?email=test@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 --:--:-- --:--:-- --:--:--     0*   Trying 172.27.0.5:8080...
* Connected to gate (172.27.0.5) port 8080
&amp;gt; GET /api/get_user/?email=test@example.com HTTP/1.1
&amp;gt; Host: gate:8080
&amp;gt; User-Agent: curl/8.2.1
&amp;gt; Accept: */*
&amp;gt;
&amp;lt; HTTP/1.1 403 Forbidden
&amp;lt; Cache-Control: private, max-age=600, stale-while-revalidate=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: $.body.email 0 16 EMAIL_ADDRESS
&amp;lt;
  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0
* Connection #0 to host gate left intact
/usr/server/app $
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;    - id: authz_allow_if_authed_pii
      type: opa
      enabled: true
      intercept: response
      parameters:
        &amp;lt;&amp;lt;: *slashid_config
        monitoring_mode: true
        policy_decision_path: /authz/allow
        policy: |
          package authz

          import future.keywords.if

          default allow := false

          key_found(obj, key) if { obj[key] }

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

          allow if not key_found(input.response.http.headers, &quot;X-Gate-Anonymize-1&quot;)
          allow if valid_signature
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s send a request with an invalid token:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -H &quot;Authorization: Bearer abc&quot; &apos;http://gate:8080/api/get_user/?email=test@example.com&apos; | jq
{
  &quot;email&quot;: &quot;test@example.com&quot;,
  &quot;id&quot;: 1,
  &quot;name&quot;: &quot;Test User&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The request passes but Gate logs the policy violation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gate-demo-gate-1        | time=2023-09-04T13:37:06Z level=info msg=OPA decision: false decision_id=d9b20a8d-da43-4786-ae15-1ec91199786d decision_provenance={0.55.0 19fc439d01c8d667b128606390ad2cb9ded04b16-dirty 2023-09-02T15:18:29Z   map[gate:{}]} plugin=opa req_path=/api/get_user/ request_host=gate:8080 request_url=/api/get_user/?email=test%40example.com

&lt;/code&gt;&lt;/pre&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.
For instance we embed an optimized version a rego interpreter vs standing up a separate OPA server.&lt;/p&gt;
&lt;p&gt;Let&apos;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&apos;s a simple benchmarking script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh
iterations=$1
url=$2

echo &quot;Running $iterations iterations for curl $url&quot;
totaltime=0.0

for run in $(seq 1 $iterations)
do
 time=$(curl $url \
    -s -o /dev/null -w &quot;%{time_total}&quot;)
 totaltime=$(echo &quot;$totaltime&quot; + &quot;$time&quot; | bc)
done

avgtimeMs=$(echo &quot;scale=4; 1000*$totaltime/$iterations&quot; | bc)

echo &quot;Averaged $avgtimeMs ms in $iterations iterations&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our demo, a request without any interception results in the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/server/app $ ./benchmark.sh 10000 &apos;http://gate:8080/api/get_user/?email=test@example.com&apos;
Running 10000 iterations for curl http://gate:8080/api/get_user/?email=test@example.com
Averaged 1.1820 ms in 10000 iterations
/usr/server/app $
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we enable PII detection and rewriting (hashing of the email address) coupled with our caching plugin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/server/app $ ./benchmark.sh 10000 &apos;http://gate:8080/api/get_user/?email=test@example.com&apos;
Running 10000 iterations for curl http://gate:8080/api/get_user/?email=test@example.com
Averaged 1.5955 ms in 10000 iterations
/usr/server/app $
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we test PII detection in monitoring mode:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/server/app $ ./benchmark.sh 10000 &apos;http://gate:8080/api/get_user/?email=test@example.com&apos;
Running 10000 iterations for curl http://gate:8080/api/get_user/?email=test@example.com
Averaged 1.5176 ms in 10000 iterations
/usr/server/app $
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Last, let&apos;s run PII detection in monitoring mode coupled with OPA like we did in the example in the previous section:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/server/app $ ./benchmark.sh 10000 &apos;http://gate:8080/api/get_user/?email=test@example.com&apos;
Running 10000 iterations for curl http://gate:8080/api/get_user/?email=test@example.com
Averaged 1.8532 ms in 10000 iterations
/usr/server/app $
&lt;/code&gt;&lt;/pre&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&apos;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=&quot;https://console.slashid.dev/signup/gate?utm_source=gatedlp-bp&quot;&gt;free account&lt;/a&gt;. If you&apos;d like to use the PII detection plugin, please contact us at at &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;contact@slashid.dev&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Rate Limiting for Large-scale, Distributed Applications and APIs Using GCRA</title><link>https://slashid.dev/blog/id-based-rate-limiting/</link><guid isPermaLink="true">https://slashid.dev/blog/id-based-rate-limiting/</guid><description>Rate limiting is a key defense against bots and threats for APIs and backends. Traditional IP-based rate limiting techniques are insufficient today because they can be easily bypassed.  In this article, we discuss the state of the art when it comes to rate limiting and how we have implemented a modern, distributed, identity-based rate limiting plugin for Gate.</description><pubDate>Mon, 16 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Guaranteeing high availability is a key aspect of API security. At a high level, a rate limiter governs how many requests a given entity (e.g., a user or an IP address) can issue in a specific window of time (e.g., 200 requests per minute).&lt;/p&gt;
&lt;p&gt;If you’re a company building web applications and APIs at large scale, a good rate limiter can prevent users and bots from harming your website’s availability with a spate of requests while also stopping spam, DDoS, scraping, and credential stuffing attacks.&lt;/p&gt;
&lt;p&gt;For modern applications it is key for any rate limiter to have the following additional features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Support for distributed rate limiting across microservices&lt;/li&gt;
&lt;li&gt;Does not appreciably slow down web requests&lt;/li&gt;
&lt;li&gt;Block/limit requests based on the identity of the request and not just the IP address&lt;/li&gt;
&lt;li&gt;Have granular control over which endpoints are rate limited&lt;/li&gt;
&lt;li&gt;Have different rate limits for different groups of customers depending on certain factors (e.g., subscription tier)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On top of the characteristics above, our implementation of rate limiting has several further advantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It supports multiple limits at the same time instead of a single one (see below for a real world example)&lt;/li&gt;
&lt;li&gt;It supports multiple storage backends depending on performance and accuracy requirements&lt;/li&gt;
&lt;li&gt;It supports the ability to use JSONPath to enforce rate limiting based on any part of the request, including JSON Web Token (JWT) claims&lt;/li&gt;
&lt;li&gt;It supports a webhook for more complex identity-based rate limiting logic&lt;/li&gt;
&lt;li&gt;It supports throttling, so requests are delayed rather than simply blocked&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;https://www.slashid.dev/products/gate&quot;&gt;Gate&lt;/a&gt; is our identity-aware edge authorizer to protect APIs and workloads that implements the rate limiting plugin we&apos;ll describe below.&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;You can find more about Gate in the developer &lt;a href=&quot;https://developer.slashid.dev/docs/gate&quot;&gt;portal&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Background: Common rate limiting approaches&lt;/h2&gt;
&lt;p&gt;The most common rate limiting algorithms are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fixed window counters&lt;/li&gt;
&lt;li&gt;Sliding window log&lt;/li&gt;
&lt;li&gt;Token bucket&lt;/li&gt;
&lt;li&gt;Leaky bucket&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Fixed window counters&lt;/h3&gt;
&lt;p&gt;In this algorithm, the rate limiter divides time into fixed windows per user and maps incoming events into these windows. If a user exceeds the allowed number of requests in a given window, the remaining requests are dropped.&lt;/p&gt;
&lt;p&gt;Implementation is straightforward - for each user/IP address:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The rate limiter splits the timeline into fixed-size time windows and assigns a counter for each window&lt;/li&gt;
&lt;li&gt;Each request increments the counter by one in the relevant window&lt;/li&gt;
&lt;li&gt;Once the counter reaches the pre-defined threshold, new requests are dropped until a new time window starts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This algorithm is rarely seen in the wild but it creates the foundation for the others we&apos;ll discuss below. Fixed window counters is the most easily scalable rate limiter and can be implemented with an atomic increment-and-get operation (e.g., &lt;code&gt;INCR&lt;/code&gt; command on Redis), unlike other algorithms that require multiple steps and therefore each check needs to be performed sequentially.&lt;/p&gt;
&lt;p&gt;The main issue with this algorithm is that it can be gamed by spiking traffic at the end of each window. In other words, since the windows are fixed, an attacker can use the fixed window and shift his request patterns by a certain delta to, in the worst case, double the allowed number of requests per window.&lt;/p&gt;
&lt;p&gt;For instance, if the rate limiter allows a maximum of 1000 requests for each one-minute window, then 1000 requests between 1:00:00 and 1:01:00 are allowed and 1000 more are allowed between 1:01:00 and 1:02:00. However, if all the requests are sent between 1:00:30 and 1:01:30, 2000 requests will be allowed in a one-minute interval.&lt;/p&gt;
&lt;h3&gt;Sliding window log&lt;/h3&gt;
&lt;p&gt;The sliding window log is another approach that addresses this issues of fixed window counters. In this algorithm, the rate limiter tracks a timestamped log for each user request. These logs are usually stored in a hash set or table that is sorted by time. Logs with older timestamps outside the window are discarded. When a new request comes in, the rate limiter counts the logs for that user to determine the request rate. If the request exceeds the threshold rate, then it is discarded.&lt;/p&gt;
&lt;p&gt;The advantage of this algorithm is that it does not suffer from the boundary conditions of fixed windows. The disadvantage is that it is very memory inefficient.&lt;/p&gt;
&lt;h3&gt;Token Bucket&lt;/h3&gt;
&lt;p&gt;Wikipedia has a good explanation of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Token_bucket#Algorithm&quot;&gt;algorithm&lt;/a&gt;.
Conceptually the idea is that you allow requests through until the &quot;bucket&quot; for that user is empty and when that happens you discard further requests until the bucket is refilled.&lt;/p&gt;
&lt;p&gt;Practically, for each user/IP address you keep track of, you would save the request timestamp and available token count.&lt;/p&gt;
&lt;p&gt;Whenever a new request arrives from a user, the rate limiter fetches the timestamp and available token count based on a chosen refill rate.
Then, it updates the tuple for the user with the current request’s timestamp and the new available token count - in other words, the delta between the last timestamp and the current one dictates how many extra tokens should be added to the bucket. When the available token count drops to zero, the rate limiter knows the user has exceeded the limit.&lt;/p&gt;
&lt;p&gt;The key benefit of this algorithm is that its memory footprint is extremely limited and its speed of execution is very fast. However depending on the implementation atomicity is hard to guarantee which could lead to some requests evading the limit.&lt;/p&gt;
&lt;h3&gt;Leaky Bucket&lt;/h3&gt;
&lt;p&gt;This algorithm is similar to the token bucket. When a request is registered, it is appended to the end of a queue. At a regular interval, the first item in the queue is processed. If the queue is full, then additional requests are discarded (leaked).&lt;/p&gt;
&lt;p&gt;The advantage of this algorithm compared to the token bucket is that it smooths out bursts of requests and processes them at an approximately constant rate.&lt;/p&gt;
&lt;p&gt;The disadvantage is that it&apos;s hard to implement in a distributed setting and it&apos;s hard to guarantee atomicity in this case as well.&lt;/p&gt;
&lt;h2&gt;The rate limiting implementation in Gate&lt;/h2&gt;
&lt;h3&gt;Chosen algorithm: Generic cell rate algorithm&lt;/h3&gt;
&lt;p&gt;For our implementation, we decided to use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm&quot;&gt;generic cell rate algorithm&lt;/a&gt; (GCRA), which is a token bucket-like algorithm.&lt;/p&gt;
&lt;p&gt;In GCRA the algorithm calculates the timestamp of the next predicted arrival time (called Theoretical Arrival Time) for a request based on the configuration parameters.
If the request is early (i.e., non-compliant), the request can either be discarded or marked for potential discard downstream, depending on the rate limiter policies.&lt;/p&gt;
&lt;p&gt;There are several advantages to GCRA over the other algorithms discussed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Memory efficiency and speed&lt;/strong&gt;: Rather than physically counting tokens, the timestamp-based method keeps track of the theoretical arrival time (TAT) for the next compliant request. When a request arrives, its arrival time is compared with the TAT. If it&apos;s before the TAT, the request is non-compliant, and if it&apos;s on or after the TAT, the request is compliant. The TAT is then updated based on the interarrival time. Contrary to queue based approaches, this is significantly more memory-efficient without the fixed windows drawbacks,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Precision&lt;/strong&gt;: In GCRA, the exact moment when the next request will be allowed is known.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Double Rate Parameters&lt;/strong&gt;: GCRA is described with two parameters: a burst rate and a sustained rate. This allows control over both the frequency of requests and the maximum number of requests that can be executed at once.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom parameters per endpoint&lt;/strong&gt;: GCRA allows to specificy different policies for different endpoints. For example, given &quot;/api/op1&quot; and &quot;/api/op2&quot;, op1 can be limited to 60 ops/minute and op2, being more expensive, can be limited to 5 ops/minute. You just need to guarantee that the &lt;code&gt;burst / rate&lt;/code&gt; (i.e., window size) remains constant when reusing the same ratelimiter&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;State storage&lt;/h3&gt;
&lt;p&gt;One key consideration when deciding on state storage is atomicity. As we described in the sections above, certain algorithms (including GCRA) are hard to implement atomically, because more than one operation has to be performed in order to calculate the rate limit.
For instance, in a naive implementation of the token bucket, if multiple requests arrive at a very close interval, non-atomicity implies a race condition between the rate limiting updating the bucket for the two requests, potentially resulting in a request above the limit passing through.
The bigger the delta between the operation, (e.g., fetching the data and updating it), the easier it is to trigger such a race condition.&lt;/p&gt;
&lt;p&gt;To address the atomicity problem, we have implemented two approaches using Redis. The first is a C++ Redis module and the second is a Lua script for Redis. In both instances, Redis guarantees the atomicity of execution.&lt;/p&gt;
&lt;p&gt;Gate supports several storage options to keep track of the rate limiter state:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;In memory&lt;/strong&gt;: Highest performance but only usable in single-process scenarios&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis module&lt;/strong&gt;: Can be distributed and has the best performance of the non-volatile options, but requires the installation of a module in Redis&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis Lua script&lt;/strong&gt;: Best trade-off between performance and easy Redis setup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-atomic Redis&lt;/strong&gt;: Executes the rate limiting logic in Gate while persisting state in Redis. It is non-atomic and may sometimes exceed the desired rate limit. Only use this if you need to keep your Redis server free of custom code.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Rate limit request fields&lt;/h3&gt;
&lt;p&gt;It is often necessary to apply independent limits per user (or groups, or any other criteria). To achieve this, the rate limit plugin supports the &lt;code&gt;&amp;lt;Limit Request Field&amp;gt;&lt;/code&gt; option. This can be used to specify a JSONPath, which will be used to retrieve the additional information necessary to differentiate between requests.&lt;/p&gt;
&lt;p&gt;A few typical examples for the &lt;code&gt;&amp;lt;Limit Request Field&amp;gt;&lt;/code&gt; JSONPath selector:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$.request.http.headers[&apos;X-User-Id&apos;][0]&lt;/strong&gt;: Retrieves a user ID from a custom header&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$.request.http.cookies[&apos;user-id&apos;]&lt;/strong&gt;: Retrieves a user ID from a cookie&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$.request.parsed_token.payload.sub&lt;/strong&gt;: Retrieves a user ID from the &lt;code&gt;sub&lt;/code&gt; claim in a JWT.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Map limit endpoint&lt;/h3&gt;
&lt;p&gt;For more complex scenarios you can use the map limit endpoint. If specified, for every incoming request the rate limiter plugin will make a &lt;code&gt;GET&lt;/code&gt; request to the specified endpoint.
The method, path and headers of the original request are forwarded to the mapping endpoint.&lt;/p&gt;
&lt;p&gt;The response must be HTTP 200 (OK) and must be JSON-formatted. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;limits&quot;: [
    {
      &quot;key&quot;: &quot;users/john.smith&quot;,
      &quot;comment&quot;: &quot;username&quot;,
      &quot;interval&quot;: 0.1, // In seconds. Use either interval or burst
      //&quot;burst&quot;:      10,  // Use either interval or burst
      &quot;window&quot;: 1, // In seconds
      &quot;max_throttle&quot;: 5 // In seconds
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;You can read more about the rate limit request field configuration in our developer &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins/ratelimit#rate-limit-differentiation&quot;&gt;portal&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Monitoring Mode&lt;/h3&gt;
&lt;p&gt;By default, this plugin will automatically reject excessive requests with HTTP error 429 (Too Many Requests), and will automatically sleep before completing the request if throttling is necessary. The plugin will also add headers that you can use to get info about the rate-limiting result.&lt;/p&gt;
&lt;p&gt;However, it is possible to let the request go through by enabling monitoring mode. It is then up to your request handler to decide how to proceed when the rate limit is exceeded or requires throttling. This allows to implement &quot;shadow ban&quot; like functionality where an attacker continues to receive a 200 HTTP response code but behind the scenes the rate limiter stopped sending real data after they exceeded the rate limit.&lt;/p&gt;
&lt;h2&gt;A brief example&lt;/h2&gt;
&lt;p&gt;Let&apos;s see a Gate configuration example to get a concrete feel of the rate limiter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gate:
  plugins:
    - id: ratelimit
      type: ratelimit
      enabled: false
      parameters:
        limiter:
          type: redis_lua
          address: redis:6379
        limits:
          - key: global
            comment: global rate
            burst: 10000
            window: 1s
          - key: username
            comment: Username
            burst: 600
            window: 1m
            max_throttle: 3s
            request_field: $.request.http.cookies[&apos;username&apos;]

  urls:
    - pattern: &apos;*/hello&apos;
      target: http://backend:8000
      plugins:
        ratelimit:
          enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Breaking this down, the first part of the configuration tells Gate that the state storage mode is Redis with the Lua script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gate:
  plugins:
    - id: ratelimit
      type: ratelimit
      enabled: false
      parameters:
        limiter:
          type: redis_lua
          address: redis:6379
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then proceed to specify the keys on which the rate limiter should operate. In our case we specify a global rate and a rate based on the &lt;code&gt;username&lt;/code&gt; of a user:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    limits:
        - key: global
        comment: global rate
        burst: 10000
        window: 1s
        max_throttle: 1s
        - key: username
        comment: Username
        burst: 600
        window: 1m
        max_throttle: 3s
        request_field: $.request.http.cookies[&apos;username&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the global case we define a window of 1 second and a burst of 10,000 requests per time window, whereas in the username case we limit the burst to 600 requests per minute.
We throttle requests (before discarding them) for 1 second in the global case and 3 seconds in the username case.
Lastly, we tell the rate limiter to locate the username field in &lt;code&gt;$.request.http.cookies[&apos;username&apos;]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, we enable the plugin on the &lt;code&gt;&quot;*/hello&quot;&lt;/code&gt; endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- pattern: &apos;*/hello&apos;
  target: http://backend:8000
  plugins:
    ratelimit:
      enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Performance&lt;/h3&gt;
&lt;p&gt;Benchmarking the above example on an AMD Ryzen 7 5800X for 30 seconds on localhost with a single instance of Gate, we observe the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/id-rate-limiting/10-threads.png&quot; alt=&quot;sidecar&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The slowdown incurred by interposing a service between the request and the backend serving “/hello” is approximately 244us and adding the rate limiter adds a further 356 us on top.
The rate limiter processes 13,180 requests per seconds.&lt;/p&gt;
&lt;p&gt;What happens when the server is maxed out and fully loaded?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/id-rate-limiting/1000-threads.png&quot; alt=&quot;sidecar&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The slowdown caused by the rate limiter goes up to 13ms when all CPUs are busy.
In terms of request per seconds, the rate limiter can process 26,542 as a single instance.&lt;/p&gt;
&lt;p&gt;While it&apos;s important to compare performance with the system under strain, in the real world, the gate instances would autoscale hence using the data from 10 client threads is a more realistic depiction of how the rate limiter would behave in practice.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Modern rate limiting is a key asset to protect against spam and bots. In this brief blog post, we&apos;ve discussed the most common implementations of rate limiting and our custom one in Gate.&lt;/p&gt;
&lt;p&gt;We’d love to hear any feedback you may have. Please contact us at &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;contact@slashid.dev&lt;/a&gt;! Try out SlashID with a &lt;a href=&quot;https://console.slashid.dev/signup/gate?utm_source=ratelimit-bp&quot;&gt;free account&lt;/a&gt;.&lt;/p&gt;
</content:encoded><author>Paulo Costa, Vincenzo Iozzo</author></item><item><title>Identity Security: The problem(s) with federation</title><link>https://slashid.dev/blog/identity-security-federation-issues/</link><guid isPermaLink="true">https://slashid.dev/blog/identity-security-federation-issues/</guid><description>Federating trust with an identity provider (IdP) is common practice to centralize identity governance.  However, attackers can exploit identity federation to breach organizations or maintain persistence in a system.  This blog post explores common attack vectors against federated identities and effective mitigation strategies.</description><pubDate>Mon, 30 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The last &lt;a href=&quot;https://www.slashid.com/blog/non-human-identity-security/&quot;&gt;blog post&lt;/a&gt; explored non-human identity (NHI) security challenges, identifying identity federation as a major attack vector. Today, we’ll take a deeper look into federation-based threats, drawing on public research and historical breaches.&lt;/p&gt;
&lt;p&gt;Notably, threat groups like APT29 and Scattered Spider frequently employ these techniques to maintain persistence and escalate privileges within their victims&apos; environments.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Identity federation vulnerabilities impact both users and non-human identities, and this post will address both as the attack vectors are often similar.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;What is identity federation?&lt;/h2&gt;
&lt;p&gt;At its core, identity federation allows multiple services to perform access control by trusting an authentication performed by a different service (generally the identity provider). This diagram offers a schematic representation of the process:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/identity-security-federation-issues/federated-identity-overview.png&quot; alt=&quot;federation&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The three most common protocols that allow identity federation are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Security Assertion Markup Language (SAML)&lt;/li&gt;
&lt;li&gt;Kerberos&lt;/li&gt;
&lt;li&gt;OpenID Connect (OIDC)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Federation can occur between a variety of applications; for example, between an IdP and a third-party SaaS provider like AWS or Snowflake, or even between two IdPs (e.g., Active Directory to Entra, or Entra to Okta). A frequent use case is your CI/CD platform (e.g., GitHub) federating with a cloud service provider (CSP) to deploy code.&lt;/p&gt;
&lt;p&gt;While the underlying protocols are complex, the key takeaway is that federating trust opens new avenues for attacks, especially if the federated relationships are exploited.&lt;/p&gt;
&lt;h2&gt;What are the threat vectors?&lt;/h2&gt;
&lt;p&gt;At a very high level, most of the federation issues can be seen as variations of the confused deputy problem. In other words: by delegating trust, we open up to an attack scenario where the adversary can compromise the IdP or install a fake IdP and leverage it to breach or maintain persistence in any environment that trusts the IdP. Note that this vulnerability is often transitive; for instance, if AWS is federated with Okta and Okta is federated with AD, compromising AD can lead to a breach in AWS.&lt;/p&gt;
&lt;p&gt;These known attack patterns have also been observed involving federation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rogue federation&lt;/li&gt;
&lt;li&gt;Token forgery&lt;/li&gt;
&lt;li&gt;Federation misconfiguration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following sections describe an example of each attack pattern.&lt;/p&gt;
&lt;h2&gt;Example 1: Scattered Spider’s rogue federation attack on Okta&lt;/h2&gt;
&lt;p&gt;Scattered Spider is one of the most prolific attackers when it comes to Okta-related intrusions. Scattered Spider&apos;s approach is often very simple: first, they compromise a privileged Okta account via MFA fatigue and phishing, then, they proceed to persist in the system by adding a rogue federated IdP into Okta. Once the rogue IdP is registered, the attacker can log in as an existing Okta user into any application the user has access to. Note that because the IdP is attacker-controlled, they can impersonate &lt;em&gt;any&lt;/em&gt; identity of their choice.&lt;/p&gt;
&lt;p&gt;This video from Adam Chester shows a proof of concept of the attack leveraging SAML for federation.&lt;/p&gt;
&lt;p&gt;&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/uw1hlKNDG2c?si=eM8VHrGc4iepZ9OU&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen /&gt;

&lt;/p&gt;
&lt;p&gt;Thankfully this attack is also reasonably easy to detect, since Okta emits an event when a new IdP is federated(&lt;code&gt;system.idp.lifecycle.created&lt;/code&gt;) and it&apos;s a low frequency event.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A variant of this attack worth mentioning leverages a valid compromised account in the IdP to breach a user in a third-party application.
The attack happens by changing the login/username of the compromised user in Okta to a valid user in the third-party application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For instance, let&apos;s imagine a compromised Okta user account called &lt;code&gt;user_A&lt;/code&gt; and a different Salesforce user &lt;code&gt;admin@acme.com&lt;/code&gt;. The attacker can change the email address of &lt;code&gt;user_A&lt;/code&gt; to appear as &lt;code&gt;admin@acme.com&lt;/code&gt; when federated into Salesforce and hence impersonate the admin account without having access to it.&lt;/p&gt;
&lt;p&gt;In Okta, you can monitor for the &lt;code&gt;application.user_membership.change_username&lt;/code&gt; event. While not very common, it is a valid action sometimes used for governance purposes by IT teams and therefore it is important to monitor the event in combination with other contextual indicators.&lt;/p&gt;
&lt;h2&gt;Example 2: Token forgery – a quieter approach&lt;/h2&gt;
&lt;p&gt;The less noisy way to achieve the same goal is via token forgery. This attack has different names in different environments, the most famous one being &quot;Golden Ticket&quot; in the context of Kerberos and Active Directory. However, this attack is not restricted to any one specific protocol or IdP as it&apos;s based on compromising the key used to generate authentication tokens.&lt;/p&gt;
&lt;p&gt;In most federation mechanisms, trust between systems is established by registering a certificate or public key representing a third-party federated system. Once that&apos;s done, an application can verify the authenticity of an incoming token by verifying its signature.&lt;/p&gt;
&lt;p&gt;An attacker able to compromise the signing keys of an IdP is thus able to forge arbitrary tokens and impersonate any user within the system.&lt;/p&gt;
&lt;p&gt;Several high-profile attacks employed token forgery. For instance, both the MGM Resorts hack and the Sunburst APT29 campaign leveraged token forgery to move laterally and breakout.&lt;/p&gt;
&lt;p&gt;As mentioned, there are several variations of this attack but two common ones are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compromise an on-prem Active Directory domain controller and take advantage of identity federation to move laterally to Entra or to another system in the cloud&lt;/li&gt;
&lt;li&gt;Add a rogue certificate or key pair to a legitimate federated IdP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, detecting token forgery is much harder than detecting a rogue IdP. Detection requires monitoring anomalous tokens and early detection of high risk indicators consistent with a potential breach.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Token forgery attacks are extremely powerful both because they are harder to detect and because they allow the impersonation of any identity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Example 3: Federation misconfiguration&lt;/h2&gt;
&lt;p&gt;Federation can also introduce risk due to misconfigurations, especially in cloud environments.&lt;/p&gt;
&lt;p&gt;Taking AWS as an example, every role has a role trust policy that defines who can assume the role and as a result perform any action the role is entitled to.
The most trivial example of a role trust policy looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;AWS&quot;: &quot;*&quot;
      },
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the policy above, any AWS account within the same account identity can assume the role.&lt;/p&gt;
&lt;p&gt;It is also possible to authenticate to a role through an external identity via two actions: &lt;code&gt;sts:AssumeRoleWithWebIdentity&lt;/code&gt; and &lt;code&gt;sts:AssumeRoleWithSAML&lt;/code&gt;. The former is used for OIDC and web providers and the latter for SAML&lt;/p&gt;
&lt;p&gt;Writing secure policies is far from trivial; consider for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;Federated&quot;: &quot;arn:aws:iam::123456789:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/40D159CDF6F2349B68846BEC03BE0428&quot;
      },
      &quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,
      &quot;Condition&quot;: {
        &quot;StringEquals&quot;: {
          &quot;oidc.eks.us-east-1.amazonaws.com/id/40D159CDF6F2349B68846BEC03BE0428:aud&quot;: &quot;sts.amazonaws.com&quot;
        }
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the role trust policy above, only an EKS cluster with id &lt;code&gt;40D159CDF6F2349B68846BEC03BE0428&lt;/code&gt; can assume the role via &lt;code&gt;AssumeRoleWithWebIdentity&lt;/code&gt;. However, the policy does not specify which identities can assume the role, which means that &lt;em&gt;any&lt;/em&gt; valid access token is going to be able to assume the role.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;These issues are present both for AWS-emitted tokens as well as token emitted by third-party IdPs that are registered with AWS (e.g., Github).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Similary to token forgery, there is not simple way to detect these issues and thus a comprehesive, contextual approach is necessary.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This blog post reviewed why identity federation is yet another source of identity-related breaches. SlashID can detect and automatically respond to the federation attacks shown above! If you are concerned about these attack vectors, talk to us to schedule a free &lt;a href=&quot;https://www.slashid.dev/contact/identity-security-assessment/&quot;&gt;Identity Security Assessment&lt;/a&gt;.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>A deep dive in the AWS credential leaks reported by Palo Alto Networks</title><link>https://slashid.dev/blog/large-scale-env-files-breach/</link><guid isPermaLink="true">https://slashid.dev/blog/large-scale-env-files-breach/</guid><description>Thousands of credentials were exfiltrated from exposed .env files in the latest large-scale attack uncovered by Palo Alto.  Protecting cloud services and non-human identities spread across many vendors and environments is now table-stakes: SlashID can help.</description><pubDate>Thu, 22 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Palo Alto Unit 42 discovered a large-scale campaign siphoning API keys and access tokens from exposed &lt;code&gt;.env&lt;/code&gt; files in Amazon Web Services (AWS) domains. The attacker seemed financially motivated and ultimately tried to ransom victims by deleting objects from their S3 buckets.&lt;/p&gt;
&lt;p&gt;These attacks are becoming increasingly prevalent and prove once again that today stolen credentials are the most common attack vector for initial access.&lt;/p&gt;
&lt;p&gt;Palo Alto wrote about the breach in detail, in this blog post we will focus on the identity component of the attack and how an identity protection framework can keep your data safe.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SlashID can help detect and remediate these attacks before the attacker has a chance to break out and cause damage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Some Stats&lt;/h2&gt;
&lt;p&gt;Palo Alto discovered over 90,000 unique environment variables leaked through exposed files, which contained access keys or IAM credentials. Of these, the top cloud and SaaS platforms identified were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1185 AWS access keys&lt;/li&gt;
&lt;li&gt;333 Paypal Oauth 2.0 credentials&lt;/li&gt;
&lt;li&gt;235 GitHub credentials&lt;/li&gt;
&lt;li&gt;111 HubSpot API keys&lt;/li&gt;
&lt;li&gt;39 Slack webhooks&lt;/li&gt;
&lt;li&gt;27 Digital Ocean tokens&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Identity Anatomy of the Attack&lt;/h2&gt;
&lt;h3&gt;Step 1: Initial Recon and Target Discovery&lt;/h3&gt;
&lt;p&gt;The attacker used Tor to perform the initial large-scale &lt;code&gt;.env&lt;/code&gt; file recon, relying on extensive automation techniques to probe over 230 million unique targets. Once the credentials were obtained, the threat actor relayed on virtual private networks (VPNs) and virtual private servers (VPSs) for lateral movement and further steps of the attack campaign.&lt;/p&gt;
&lt;p&gt;After stealing variables and credentials from the &lt;code&gt;.env&lt;/code&gt; files, the attacker exploited several well-known AWS APIs to learn more about the environment and to identify targets to exploit; in particular:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GetCallerIdentity&lt;/li&gt;
&lt;li&gt;ListUsers&lt;/li&gt;
&lt;li&gt;ListBuckets&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Step 2: Privilege Escalation&lt;/h3&gt;
&lt;p&gt;After the target discovery phase, the attacker used the &lt;code&gt;CreateRole&lt;/code&gt; API to create a new IAM role named &lt;code&gt;lambda-ex&lt;/code&gt;. Then, they were able to attach to it the AWS-managed &lt;code&gt;AdministratorAccess&lt;/code&gt; policy with an &lt;code&gt;AttachRolePolicy&lt;/code&gt; API call, thus granting access to all resources within the AWS account.&lt;/p&gt;
&lt;h3&gt;Step 3: Code Execution&lt;/h3&gt;
&lt;p&gt;The attacker tried several methods to create the infrastructure stack needed to execute malicious code, failing with EC2 but ultimately succeeding in creating multiple lambda functions with AWS Lambda. The lambda functions, attached to the newly created &lt;code&gt;lambda-ex&lt;/code&gt; IAM role, were then used to perform internet-wide scanning looking for more unprotected &lt;code&gt;.env&lt;/code&gt; files.&lt;/p&gt;
&lt;h2&gt;Detection, Response, and Prevention&lt;/h2&gt;
&lt;h3&gt;Detection&lt;/h3&gt;
&lt;p&gt;Writing detection queries is a tricky art because distinguishing signal from noise is often not trivial.&lt;/p&gt;
&lt;p&gt;We believe that an identity-first approach is the easiest path to detection. In particular, the Unit 42 research showed that Tor was used to perform the initial recon and target discovery. While noisy, looking for &lt;code&gt;GetCallerIdentity&lt;/code&gt; and &lt;code&gt;ListUsers&lt;/code&gt; in combination with known-bad or suspicious IP addresses could have helped detect the attack early on.&lt;/p&gt;
&lt;p&gt;The privilege escalation step in the chain is one of the higher signal operations that the attacker performed was the combination of &lt;code&gt;CreateRole&lt;/code&gt; with &lt;code&gt;AttachRolePolicy&lt;/code&gt; using &lt;code&gt;AdministratorAccess&lt;/code&gt; as the selected policy. Coupled with appropriate identity posture management, this set of API calls makes for a solid, high-quality, detection.&lt;/p&gt;
&lt;h3&gt;Response&lt;/h3&gt;
&lt;p&gt;Prompt remediation is key to avoid attackers breaking out and, in this case, destroying the content of S3 buckets for ransom. In this case, the blast radius could have been reduced by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Rotating credentials on detection of &lt;code&gt;GetCallerIdentity&lt;/code&gt; or &lt;code&gt;ListUsers&lt;/code&gt; API calls from known-bad or suspicious IP addresses;&lt;/li&gt;
&lt;li&gt;Flagging or suspending the newly-created role based on the highly privileged policy requested.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Prevention&lt;/h3&gt;
&lt;p&gt;As Palo Alto highlighted in their article, good hygiene goes a long way to preventing these kinds of attacks. Organizations can deploy several countermeasures early on to reduce the risk of a breach and reduce the blast radius if a breach happens:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Avoid using long-lived credentials&lt;/li&gt;
&lt;li&gt;Adopt least-privilege and limit the number of identities that can escalate privileges (e.g., that can call &lt;code&gt;AttachRolePolicy&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How SlashID Helps&lt;/h2&gt;
&lt;p&gt;SlashID Identity Protection can detect and respond to these identity-based attacks by running automatic remediation workflows following a detection trigger. For instance, you can automatically rotate credentials if they are used by a malicious IP address or suspend and delete an identity if it is created with high privileges.&lt;/p&gt;
&lt;p&gt;Last, SlashID can help enforce the least privilege by detecting over-privileged identities that can be exploited for privilege escalation attack techniques.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.slashid.dev/contact/&quot;&gt;Contact us&lt;/a&gt; to schedule a free Identity Protection assessment and understand how SlashID can help secure your environment.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>React SDK support for &lt;Groups&gt;</title><link>https://slashid.dev/blog/groups-react/</link><guid isPermaLink="true">https://slashid.dev/blog/groups-react/</guid><description>With the latest React SDK release we are introducing a new control component, &lt;Groups&gt;. You can use &lt;Groups&gt; to conditionally render parts of the UI depending on whether the authenticated user belongs to specific Groups.</description><pubDate>Thu, 09 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;With the latest React SDK release we are introducing a new control component: &lt;code&gt;&amp;lt;Groups&amp;gt;&lt;/code&gt;. Using &lt;code&gt;&amp;lt;Groups&amp;gt;&lt;/code&gt;, you can conditionally render parts of the UI depending on whether the authenticated user belongs to specific Groups.&lt;/p&gt;
&lt;p&gt;Read on to learn how to use this feature to implement &lt;a href=&quot;https://en.wikipedia.org/wiki/Role-based_access_control&quot;&gt;role based access control&lt;/a&gt; (RBAC) in your frontend!&lt;/p&gt;
&lt;p&gt;&lt;video style=&quot;width:100%&quot; autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/blog/groups-react/groups_video.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&gt;
&lt;/p&gt;
&lt;h2&gt;SlashID groups in a nutshell&lt;/h2&gt;
&lt;p&gt;Groups are named sets of &lt;a href=&quot;https://developer.slashid.dev/docs/access#persons&quot;&gt;Persons&lt;/a&gt; and can be thought of as roles in an RBAC context.&lt;/p&gt;
&lt;p&gt;You can create any number of groups, and a Person can belong to multiple Groups.&lt;/p&gt;
&lt;p&gt;When a Person authenticates with SlashID in your frontend, you receive back a token that includes a claim with the Groups the Person belongs to. The name of the claim is ‘groups’ by default and can be customized as part of your Organization’s configuration. As discussed in our last &lt;a href=&quot;https://www.slashid.dev/blog/google-groups/&quot;&gt;blogpost&lt;/a&gt;, SlashID can also pull groups from a third-party identity provider, such as Google.&lt;/p&gt;
&lt;p&gt;Each SlashID customer is a tenant with one or more organizations. Groups can be either:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Specific to Organizations; or&lt;/li&gt;
&lt;li&gt;Shared across multiple Organizations.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the second case, Group names are visible to all relevant Organizations through the Groups API, but Group membership is Organization-specific.&lt;/p&gt;
&lt;p&gt;We’ll discuss sub-organizations and group propagation in a separate blogpost, but for now let’s show an example of Organization-specific Groups.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a Group called &lt;code&gt;admin&lt;/code&gt; in &lt;code&gt;test_organization_1&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X POST &apos;https://api.sandbox.slashid.com/groups&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: f978a6bd-adcb-3268-2312-573d0bad155d&apos; \
-H &apos;SlashID-API-Key: ri6qRvmuLc1IlUeQm732126WvAHUIHUIO=&apos; \

--data-raw &apos;{
 &quot;name&quot;: &quot;admin&quot;,
 &quot;description&quot;: &quot;special access&quot;
}&apos;

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Add a Person, specified by uuid, to the &lt;code&gt;admin&lt;/code&gt; Group:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X POST &apos;https://api.sandbox.slashid.com/groups/admin/persons&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: f978a6bd-adcb-3268-2312-573d0bad155d&apos; \
-H &apos;SlashID-API-Key: ri6qRvmuLc1IlUeQm732126WvAHUIHUIO=&apos; \
--data-raw &apos;{
 &quot;persons&quot;: [
&quot;217b3e73-b0bb-4387-8ea4-85c8f2d04c93&quot;
 ]
}&apos;```

3. When the Person with that `id` authenticates with `test_organization_1`, the token will include a claim:

```json
“groups”: [“admin”]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Any app consuming the token will then be able to take appropriate actions based on the Groups (e.g., visualizing sensitive information, modifying third-party data and so on ).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Check out our &lt;a href=&quot;https://developer.slashid.dev/docs/category/api/groups&quot;&gt;documentation&lt;/a&gt; for more information on Groups.&lt;/p&gt;
&lt;h2&gt;Write your own rendering rules&lt;/h2&gt;
&lt;p&gt;Now that you set up your Groups, you can implement Group-based conditional rendering in your application. When a Person authenticates, they get back a Person token. SlashID embeds the list of Group names the Person belongs to in that token. Since SlashID SDK keeps the Person token in memory, you can get information on the client side synchronously, without starting a network call.&lt;/p&gt;
&lt;p&gt;Group-based conditional rendering is demonstrated in the following code snippet. There we want to render some content only if the authenticated Person belongs to the admin group. We pass that content as children to &lt;code&gt;&amp;lt;Groups&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Groups } from &apos;@slashid/react&apos;

function Component() {
  const belongsToAdmin = React.useCallback(
    (groups) =&amp;gt; groups.includes(&apos;admin&apos;),
    []
  )

  return &amp;lt;Groups belongsTo={belongsToAdmin}&amp;gt;Only visible for admins.&amp;lt;/Groups&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You also need to pass the belongsTo callback prop - let’s look into that in more detail.&lt;/p&gt;
&lt;h3&gt;Specifying the belongsTo callback&lt;/h3&gt;
&lt;p&gt;In order to determine whether child components should be rendered, you must specify a belongsTo callback. This function will be called by the &lt;code&gt;&amp;lt;Groups&amp;gt;&lt;/code&gt; component, passing the list of groups the user belongs to as the argument.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const belongsToAdmin = React.useCallback(
  (groups) =&amp;gt; groups.includes(&apos;admin&apos;),
  []
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes &lt;code&gt;&amp;lt;Groups&amp;gt;&lt;/code&gt; very flexible: you can specify fine-grained business rules in the callback. It just needs to return a boolean value determining if the child components will be rendered or not. We recommend you memoize the callback using React.useCallback in order to minimize the number of re-renders.&lt;/p&gt;
&lt;p&gt;For a more detailed example, please check &lt;a href=&quot;https://codesandbox.io/s/groups-example-dwrp87?file=/src/App.js&quot;&gt;this codesandbox&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this blogpost we’ve shown how you can use the &lt;code&gt;&amp;lt;Groups&amp;gt;&lt;/code&gt; component to implement a simple RBAC-like web application.&lt;/p&gt;
&lt;p&gt;We have a lot more to come on Authorization, please stay tuned!&lt;/p&gt;
</content:encoded><author>Ivan Kovic, Vincenzo Iozzo</author></item><item><title>JWT Implementation Pitfalls, Security Threats, and Our Approach to Mitigate Them</title><link>https://slashid.dev/blog/jwt-risks/</link><guid isPermaLink="true">https://slashid.dev/blog/jwt-risks/</guid><description>JSON Web Tokens (JWTs) are one of the most common ways to transfer identity claims and prove the identity of a user or an entity. JWTs have become very popular in recent years because they are easy to use, read, and debug.  JWTs provide a lot of flexibility at the expense of several security risks that are often overlooked. In this article, we&apos;ll discuss common risks when implementing or manipulating JWTs and our approach to avoiding them.</description><pubDate>Thu, 21 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&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;/p&gt;
&lt;pre&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvZSBTbGFzaElEIiwiaWF0IjoxNTE2MjM5MDIyfQ.4cL42NsNCXLPEvmvNGxHN3wLuarpp98wwezHnSt2fqg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;comprises the following parts&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;table-wide&quot;&amp;gt;&lt;/p&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;{ &quot;alg&quot;: &quot;HS256&quot;, &quot;typ&quot;: &quot;JWT&quot;}&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;{&quot;sub&quot;: &quot;1234567890&quot;, &quot;name&quot;: &quot;SlashID User&quot;, &quot;iat&quot;: 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;p&gt;&amp;lt;/div&amp;gt;&lt;/p&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&apos;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;/p&gt;
&lt;pre&gt;&lt;code&gt;signAndhash(base64UrlEncode(header) + &apos;.&apos; + base64UrlEncode(payload))
&lt;/code&gt;&lt;/pre&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=&quot;https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms&quot;&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&apos;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 (&quot;claims&quot;) as a JSON object that can be transferred between two parties.
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&apos;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 &quot;none&quot;&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&apos;s see some of the most common issues with JWTs.&lt;/p&gt;
&lt;h3&gt;The &quot;none&quot; 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;&quot;Billion hashes attack&quot;&lt;/h3&gt;
&lt;p&gt;Tervoort recently disclosed at Black Hat a new attack &lt;a href=&quot;https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf&quot;&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=&quot;https://en.wikipedia.org/wiki/PBKDF2&quot;&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&apos;s crucial that this secret can&apos;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&apos;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 &quot;type confusion&quot; 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
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&apos;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 `jku`` 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&apos;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&apos;ve discussed some of the implementation details of our we mint and verify JWT &lt;a href=&quot;https://www.slashid.dev/blog/tink-jwt-ecdsa/&quot;&gt;tokens&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But we&apos;ve gone further and added a JWT verification plugin to &lt;a href=&quot;https://www.slashid.dev/products/gate&quot;&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=&quot;https://developer.slashid.dev/docs/gate/plugins/validate-jwt&quot;&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&apos;s see an example in action.&lt;/p&gt;
&lt;h3&gt;Gate configuration&lt;/h3&gt;
&lt;p&gt;First, let&apos;s look at an example using SlashID as the issuer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
    - id: validator
      type: validate-jwt
      enabled: false
      parameters:
        &amp;lt;&amp;lt;: *slashid_config
        jwks_url: https://api.slashid.com/.well-known/jwks.json
        jwks_refresh_interval: 15m
        jwt_expected_issuer: https://api.slashid.com

urls:
    - pattern: &quot;*/api/echo&quot;
      target: http://backend:8000
      plugins:
        validator:
          enabled: true
    - pattern: &quot;*/api/admin_abac&quot;
      target: http://backend:8000
      plugins:
        validator:
          enabled: true
          parameters:
            token_schema: |
              patternProperties:
                user_roles:
                  contains:
                    const: admin
              required:
                - user_roles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the configuration, we tell the plugin that the JWKS URL is &lt;code&gt;https://api.slashid.com/.well-known/jwks.json&lt;/code&gt;, and the keys should be refreshed every 15 minutes.
We also specify that the issuer has to be &lt;code&gt;https://api.slashid.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the URLs, for the &lt;code&gt;*/api/echo&lt;/code&gt; path we simply allow requests that have a valid JWT in the Authorization header.&lt;/p&gt;
&lt;p&gt;For the &lt;code&gt;*/api/admin_abac&lt;/code&gt;, we also specify that the plugin should check for the presence of a &lt;code&gt;user_roles&lt;/code&gt; claim in the JWT and that claim should contain the string &lt;code&gt;admin&lt;/code&gt;.
This is an easy way to enforce ABAC on an endpoint.&lt;/p&gt;
&lt;p&gt;If we were to use a third-party issuers instead of SlashID, the plugin instance configuration instance would change slightly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- id: validator
  type: validate-jwt
  enabled: false
  parameters:
    jwks_url: https://issuer.example.com/.well-known/jwks.json
    jwks_refresh_interval: 15m
    jwt_expected_issuer: https://issuer.example.com
    jwt_valid_cert: &amp;lt;CERT signature&amp;gt;
    jwt_allowed_algorithms:
      - &apos;ES256&apos;
      - &apos;ES512&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The difference is that for third-party issuers we need to specify the &lt;code&gt;jwt_allowed_algorithms&lt;/code&gt; and a valid &lt;code&gt;jwt_valid_cert&lt;/code&gt; URL signature.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this brief post we&apos;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
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=&quot;https://console.slashid.dev/signup/gate?utm_source=jwtrisk-bp&quot;&gt;free account&lt;/a&gt;.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>GDPR Compliance: Consent Management</title><link>https://slashid.dev/blog/gdpr-consent-management-react/</link><guid isPermaLink="true">https://slashid.dev/blog/gdpr-consent-management-react/</guid><description>Effortless GDPR compliance out of the box. Notify users about your intent to use cookies and request their consent.</description><pubDate>Fri, 27 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&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&apos; &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/data-protection-compliance/gdpr&quot;&gt;General Data Protection Regulation (GDPR) and ePrivacy Directive&lt;/a&gt; legislations. We deeply value your users&apos; privacy.&lt;/p&gt;
&lt;p&gt;In this blog post, we&apos;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=&quot;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.&quot;&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=&quot;https://www.slashid.dev/products/vault/&quot;&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 &quot;necessary cookies&quot;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;GDPRConsentDialog /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/blog/gdpr-consent-management-react/default.png&quot; alt=&quot;The default GDPRConsentDialog state&quot; /&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;img src=&quot;/blog/gdpr-consent-management-react/customize.png&quot; alt=&quot;Consent can be customized&quot; /&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;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;GDPRConsentDialog necessaryCookiesRequired /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/blog/gdpr-consent-management-react/necessary.png&quot; alt=&quot;Necessary cookies can be required&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We&apos;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&apos;re in luck! We have support for these edge-cases and more: &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-gdprconsentdialog&quot;&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;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useGDPRConsent, GDPRConsentLevel } from &apos;@slashid/react&apos;
import { useAnalytics } from &apos;...&apos;

function Component() {
  const { consents, isLoading } = useGDPRConsent()
  const { publish } = useAnalytics()

  if (isLoading) {
    return &amp;lt;div&amp;gt;Loading consent...&amp;lt;/div&amp;gt;
  }

  const analyticsAllowed = consents.some(
    ({ consent_level }) =&amp;gt; consent_level === GDPRConsentLevel.Analytics
  )

  const addToCart = () =&amp;gt; {
    if (analyticsAllowed) {
      publish({
        name: &apos;add_to_cart&apos;,
        payload: {
          /* ... */
        },
      })
    }

    // ...
  }

  return &amp;lt;button onClick={addToCart}&amp;gt;Add to cart&amp;lt;/button&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might find yourself in a position where you would like to offer your users the option to &quot;upgrade&quot; 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;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useGDPRConsent, GDPRConsentLevel } from &apos;@slashid/react&apos;
import { useAnalytics } from &apos;...&apos;

function Component() {
  const { consents, isLoading, updateGdprConsent } = useGDPRConsent()

  if (isLoading) {
    return &amp;lt;div&amp;gt;Loading consent...&amp;lt;/div&amp;gt;
  }

  const analyticsAllowed = consents.some(
    ({ consent_level }) =&amp;gt; consent_level === GDPRConsentLevel.Analytics
  )

  const enableAnalytics = () =&amp;gt; {
    updateGdprConsent([
      ...consents,
      GDPRConsentLevel.Analytics
    ])
  }

  return (
    &amp;lt;div&amp;gt;
      {!analyticsAllowed &amp;amp;&amp;amp; (
        &amp;lt;p&amp;gt;
          You have analytics disabled, your user dashboard has insufficient data to operate.
        &amp;lt;/p&amp;gt;
        &amp;lt;button onClick={enableAnalytics}&amp;gt;
          Enable analytics
        &amp;lt;/button&amp;gt;
      )}

      {/* ... */}
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&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=&quot;https://console.slashid.dev/signup?utm_source=consent-bp&quot;&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=&quot;mailto:contact@slashid.dev&quot;&gt;Let us know&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Jake Whelan, Vincenzo Iozzo</author></item><item><title>Introducing the SlashID Local Deployment</title><link>https://slashid.dev/blog/local-emulator/</link><guid isPermaLink="true">https://slashid.dev/blog/local-emulator/</guid><description>The SlashID local deployment is our answer for developers looking to build, run and test apps locally.  Local development with SlashID can be a good fit for your evaluation, prototyping, development, and continuous integration workflows.</description><pubDate>Mon, 24 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Authentication is a critical component of modern applications, but integrating and testing authentication services can be challenging, especially in local development environments. Today, we&apos;re excited to introduce the SlashID Local Deployment, a powerful tool designed to streamline your development process and enhance your ability to build, run and test applications that use SlashID authentication services.&lt;/p&gt;
&lt;h2&gt;The SlashID Local Deployment&lt;/h2&gt;
&lt;p&gt;The SlashID Local Deployment is a lightweight, easy-to-use tool that mimics our cloud-based authentication services in your local environment. Here&apos;s why it&apos;s a game-changer for developers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;On-prem deployments&lt;/strong&gt;: You can deploy our local deployment for air-gapped environments or any other scenario where you need to host your IdP.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Offline Development&lt;/strong&gt;: With the local deployment, you can develop and test your application&apos;s authentication flow without an internet connection. This is particularly useful for developers working in environments with limited or unreliable internet access.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Faster Development Cycles&lt;/strong&gt;: By eliminating the need to constantly communicate with remote servers, the local deployment significantly reduces latency and speeds up your development process. You can iterate quickly on your authentication logic without waiting for network requests to complete.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent Testing Environment&lt;/strong&gt;: The deployment provides a stable, predictable environment for testing. This consistency is crucial for reproducing and fixing authentication-related issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost-Effective&lt;/strong&gt;: During the development phase, you can reduce API calls to our cloud services, potentially lowering costs associated with high-volume testing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD Integration&lt;/strong&gt;: Easily integrate authentication testing into your continuous integration and deployment pipelines without the need for complex mock setups or dependencies on external services.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customizable Scenarios&lt;/strong&gt;: The deployment allows you to easily set up and test various authentication scenarios, including edge cases that might be difficult to reproduce in a production environment.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;h3&gt;Where does it run?&lt;/h3&gt;
&lt;p&gt;The SlashID Local Deployment is a single docker image available to our Enterprise customers.&lt;/p&gt;
&lt;h3&gt;Dependencies&lt;/h3&gt;
&lt;p&gt;The deployment depends on Postgres, Redis, and PubSub. SlashID provides the image for PubSub and a reference docker compose.&lt;/p&gt;
&lt;h3&gt;Tracking changes&lt;/h3&gt;
&lt;p&gt;We release a new version of the Emulafor for each new API release.&lt;/p&gt;
&lt;h3&gt;What&apos;s included in the deployment?&lt;/h3&gt;
&lt;p&gt;All exposed SlashID developer APIs are included in the deployment.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Developers often face challenges when working with cloud services, particularly in offline environments. This complexity can lead to inefficient development processes and increased costs. In the realm of Identity and Access Management (IAM), these hurdles frequently push developers towards creating in-house solutions, which are time-consuming to maintain and less secure.&lt;/p&gt;
&lt;p&gt;The SlashID Local Deployment bridges this gap, offering developers the ideal balance between local development convenience and the robustness of a SaaS solution. It enables seamless offline development and testing, accelerating the development cycle while maintaining the scalability and reliability of cloud-based authentication services. This approach not only enhances productivity but also ensures that developers can leverage our SaaS offering without compromising on development speed or flexibility.&lt;/p&gt;
</content:encoded><author>SlashID Team, Vincenzo Iozzo</author></item><item><title>Secure API and M2M Access with OAuth2 Client Credentials and SlashID&apos;s sidecar</title><link>https://slashid.dev/blog/m2m-auth/</link><guid isPermaLink="true">https://slashid.dev/blog/m2m-auth/</guid><description>The recent Hugging Face breach is yet another reminder that securing machine-to-machine communication and API access is essential today.  By leveraging OAuth2 Client Credentials, you can enhance security, enable fine-grained access control, simplify credential management, and benefit from a standards-based approach.</description><pubDate>Mon, 03 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Machine-to-machine (M2M) communication has become increasingly prevalent in modern application architectures, where different systems and services need to interact securely. Traditional authentication and authorization methods, such as shared secrets or mTLS, suffer from many shortcomings, ranging from a lack of fine-grained control to performance considerations.&lt;/p&gt;
&lt;p&gt;At SlashID, we support several ways to implement M2M and API authentication and authorization. One of the fastest and most effective methods is by leveraging OAuth 2.0 Client Credentials.&lt;/p&gt;
&lt;p&gt;In this blog post, we&apos;ll show you how to use SlashID to implement M2M authentication and authorization in a Kubernetes cluster.&lt;/p&gt;
&lt;h2&gt;Why OAuth 2.0 client credentials&lt;/h2&gt;
&lt;p&gt;M2M authentication and authorization is still largely an open problem. Over the years many attempts have been made to standardize the process from simple shared secrets to mTLS to SPIRE/SPIFFE. We believe that the OAuth 2.0 approach is superior for a few reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Strong Security&lt;/strong&gt;: Compared to using shared secrets or API keys, OAuth2 Client Credentials provides a more secure authentication mechanism. Access tokens are short-lived and can be easily revoked, reducing the risk of long-term exposure if a token is compromised. Additionally, client credentials are securely stored and managed within SlashID, minimizing the risk of credential leakage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Granular Access Control&lt;/strong&gt;: OAuth2 scopes enable fine-grained access control, allowing you to define specific permissions for different API resources. This ensures that machine clients only have access to the resources they need, following the principle of least privilege.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Layer 7 vs Layer 3&lt;/strong&gt;: Enforcing authorization policies at the same layer as the application logic allows for better finer-grained authorization and easier monitoring.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Standards-based Approach&lt;/strong&gt;: OAuth2 Client Credential is a widely adopted and standardized authentication and authorization flow. This further reduces the dependency on SlashID, any IdP can be used instead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Interoperability&lt;/strong&gt;: OAuth2 Client Credentials are supported by various tools, libraries, and frameworks, making it easier to integrate with different systems and languages. This interoperability allows for seamless integration of machine-to-machine authentication and authorization across diverse environments and technologies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;North-South and East-West&lt;/strong&gt;: In addition to protecting east-west traffic, through this approach, we can easily protect user-facing APIs as well as shown in this &lt;a href=&quot;https://www.slashid.dev/blog/openapi_oauth2_gate/&quot;&gt;blog post&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The setup&lt;/h2&gt;
&lt;p&gt;In this example, we have a simple Kubernetes cluster that looks like the picture below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/m2m-auth/cluster.png&quot; alt=&quot;cluster&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The frontend sends requests to the backend Gate service (&lt;code&gt;gate-microservices&lt;/code&gt;), which then dispatches requests as required to several services in the deployment.&lt;/p&gt;
&lt;p&gt;Our goal is to allow requests to the echo service only if they come from the frontend service and deny them otherwise.&lt;/p&gt;
&lt;h2&gt;Introducing SlashID Gate OAuth 2.0 plugins&lt;/h2&gt;
&lt;p&gt;Gate has two OAuth 2.0 plugins, the &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins/create-oauth2-tokens&quot;&gt;OAuth 2.0 Token Creator&lt;/a&gt; and the &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins/validate-oauth2-tokens&quot;&gt;OAuth 2.0 Token Validator&lt;/a&gt;. As their names suggest, one can be used to add an OAuth 2.0 access token to a request, and the other to validate an OAuth 2.0 token in an incoming request. Further through our plugins we can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allow requests only from specific client ids&lt;/li&gt;
&lt;li&gt;Allow requests only from specific IP addresses&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In our scenario, we leverage both to enable M2M authentication and authorization. In a simplified picture, this is the deployment we are aiming for:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/m2m-auth/sidecar.png&quot; alt=&quot;sidecar&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Specifically, the &lt;code&gt;backend-echo&lt;/code&gt; service comes with a Gate sidecar that verifies OAuth 2.0 access tokens in incoming requests as shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
  plugins:
 - id: validate_oauth2_requests
      type: validate-oauth2-token
      enabled: false
      parameters:
        header_with_token: SlashID_Signature
        token_introspection_client_id: 0661f429-e6d8-7619-a900-3122cb349922
        token_introspection_client_secret: &amp;lt;secret&amp;gt;
        token_format: opaque
        token_introspection_url: &quot;https://api.slashid.com/oauth2/tokens/introspect&quot;
        required_scopes:
            - admin
        allowed_client_ids:
            - 0662337e-05b3-7fb5-ae00-b9119ba17644
        allowed_ip_addresses:
            - 172.26.0.8
...
  urls:
    # The /api/echo endpoint is used as a demonstration of the anonymizer and oauth 2.0 validator plugins
 - pattern: &quot;*/api/echo*&quot;
      target: http://localhost:8080
      plugins:
        validate_oauth2_requests:
          enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;This configuration requires a valid OAuth 2.0 access token generated from the client ID &lt;code&gt;0662337e-05b3-7fb5-ae00-b9119ba17644&lt;/code&gt; with &lt;code&gt;admin&lt;/code&gt; as a scope for the request to reach the &lt;code&gt;*/api/echo*&lt;/code&gt; endpoints coming from the IP address &lt;code&gt;172.26.0.8&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;On the sender side, the &lt;code&gt;gate-web&lt;/code&gt; instance is configured to add an OAuth 2.0 access token to all requests:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  plugins:
 - id: add_authnz
      type: request-oauth2-authenticator
      enabled: true
      parameters:
        slashid_api_key: {{ .env.SLASHID_API_KEY }}
        slashid_org_id: {{ .env.SLASHID_ORG_ID }}
        header_for_token: &quot;SlashID_Signature&quot;
        oauth2_token_format: &quot;opaque&quot;
        oauth2_token_creation_endpoint: &quot;https://api.slashid.com/oauth2/tokens&quot;
        oauth2_token_client_id: &quot;0662337e-05b3-7fb5-ae00-b9119ba17644&quot;
        oauth2_token_client_secret: &amp;lt;secret&amp;gt;
        oauth2_scopes:
            - admin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is what a full, authenticated, roundtrip looks like - The sender adds the OAuth 2.0 access token to the request:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/m2m-auth/sender.png&quot; alt=&quot;sidecar&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The receiver verifies the token before forwarding it to the backend service:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/m2m-auth/receiver.png&quot; alt=&quot;sidecar&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With this setup we can guarantee that only requests originating from the front end reach the echo backend, restricting access to a specific client id and IP address.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Beyond the sidecar model&lt;/h2&gt;
&lt;p&gt;What we&apos;ve shown in this example is just one of the many topologies we can use Gate in, for instance, you could achieve the same result using Gate as an external authorizer for &lt;a href=&quot;https://developer.slashid.dev/docs/gate/getting-started/deploying/extauth&quot;&gt;Istio/Envoy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Further, as we&apos;ve shown in a previous blog post the OAuth 2.0 request validator plugin can be combined with our powerful OpenAPI parser to automatically enforce OAuth 2.0 client credentials authentication and authorization on your user-facing &lt;a href=&quot;https://www.slashid.dev/blog/openapi_oauth2_gate/&quot;&gt;APIs&lt;/a&gt; meaningfully increasing your API security posture compared to simple API keys which are hard to rotate, can&apos;t be used for authorization and are long-lived.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this blog post, we&apos;ve shown how we can leverage Gate to implement M2M authentication and authorization using OAuth 2.0 client credentials. We’d love to hear any feedback you may have. Please &lt;a href=&quot;https://www.slashid.dev/contact/&quot;&gt;contact us&lt;/a&gt; if you are interested in securing your APIs and machines.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Detecting Man-in-the-Middle Attacks with SlashID</title><link>https://slashid.dev/blog/mitm-detection/</link><guid isPermaLink="true">https://slashid.dev/blog/mitm-detection/</guid><description>Detect when attackers access your website through malicious proxies with SlashID.</description><pubDate>Mon, 26 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The Problem SlashID is Solving&lt;/h2&gt;
&lt;p&gt;According to Verizon&apos;s &lt;a href=&quot;https://www.verizon.com/business/resources/reports/dbir/2024/summary-of-findings/&quot;&gt;Data Breach Investigation Report&lt;/a&gt;, stolen credentials accounted for approximately 40% of breaches in 2023.
One of the most common techniques to steal credentials is a man-in-the-middle (MITM) or adversary-in-the-middle (AITM) attack.&lt;/p&gt;
&lt;p&gt;A (&lt;a href=&quot;https://owasp.org/www-community/attacks/Manipulator-in-the-middle_attack&quot;&gt;MITM&lt;/a&gt;) attack happens when a threat actor inserts themselves between two parties (often a user and an application), intercepting and/or altering the communication between the two for malicious purposes, such as stealing payment information or making unauthorized purchases.
Today there is an abundance of readily available &lt;a href=&quot;https://aware7.com/blog/the-12-best-tools-for-phishing-simulations/&quot;&gt;tools&lt;/a&gt; to do just that and performing an attack using an MITM proxy has become almost trivial, with evilnginx2 and modlishka being two of the more popular frameworks.&lt;/p&gt;
&lt;p&gt;Implementing a robust detection mechanism is essential to prevent a potential breach or remediate an ongoing one, and that&apos;s where SlashID can help you with the new MITM detection capability.&lt;/p&gt;
&lt;p&gt;Read on to learn how to detect MITM proxy attacks on your websites or third-party Identity Providers and applications with SlashID.&lt;/p&gt;
&lt;h2&gt;Enable MITM Detections on your Website&lt;/h2&gt;
&lt;p&gt;First, you need to create an MITM token in the SlashID &lt;a href=&quot;https://console.slashid.dev/security/mitm&quot;&gt;Console&lt;/a&gt;. You will need to provide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a list of allowed domains your application can be accessed on&lt;/li&gt;
&lt;li&gt;a tag that will be included in any detections to easily identify which of your applications was attacked&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/blog/mitm-detection/mitm-detection-setup.png&quot; alt=&quot;mitm setup&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once you submit this information, SlashID will generate an MITM token that you can embed in your website as a single line of &lt;code&gt;html&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/mitm-detection/mitm-detection-token.png&quot; alt=&quot;mitm setup&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And that&apos;s it: SlashID will now detect MITM attacks performed by proxying your application from a domain not explicitly allowed in the MITM token you just created.&lt;/p&gt;
&lt;h2&gt;Identity provider integrations&lt;/h2&gt;
&lt;p&gt;Login pages of well-known Identity providers can also be a target of an MITM proxy attack. SlashID can help you detect these attacks by embedding the MITM token in the custom CSS or HTML of the login page. Here we outline how to set this up for &lt;a href=&quot;https://www.okta.com/&quot;&gt;Okta&lt;/a&gt; and &lt;a href=&quot;https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id&quot;&gt;Microsoft Entra ID&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Okta&lt;/h3&gt;
&lt;p&gt;Okta allows customizing their sign-in page with a &lt;a href=&quot;https://developer.okta.com/docs/guides/custom-widget/main/#use-the-code-editor&quot;&gt;code editor&lt;/a&gt;, assuming that you configured a custom domain for the sign-in page.&lt;/p&gt;
&lt;p&gt;This integration requires the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ensure a &lt;a href=&quot;https://developer.okta.com/docs/guides/custom-url-domain/main/&quot;&gt;custom domain&lt;/a&gt; is set up in order to enable the code editor&lt;/li&gt;
&lt;li&gt;ensure the &lt;a href=&quot;https://developer.okta.com/docs/guides/custom-widget/main/#content-security-policy-csp-for-your-custom-domain&quot;&gt;Content Security Policy&lt;/a&gt; will allow downloading images from &lt;code&gt;https://d3ucf2ccdze1i2.cloudfront.net&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;use the &lt;a href=&quot;https://developer.okta.com/docs/guides/custom-widget/main/#use-the-code-editor&quot;&gt;code editor&lt;/a&gt; to add the SlashID MITM token HTML snippet anywhere within the &lt;code&gt;body&lt;/code&gt; element&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Microsoft Entra ID&lt;/h3&gt;
&lt;p&gt;Microsoft Entra ID allows uploading custom CSS to style the sign-in page. In this case, we cannot use the full HTML snippet. Instead, we&apos;ll use only the relevant CSS property - &lt;code&gt;background: url(&apos;{MITM_TOKEN}.png&apos;&lt;/code&gt; and apply it using a selector of your choice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;start from the &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/fundamentals/reference-company-branding-css-template&quot;&gt;reference company branding CSS template&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;write a rule consisting of one of the documented selectors and the &lt;code&gt;background: url(&apos;{MITM_TOKEN}.png&apos;&lt;/code&gt; property&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/fundamentals/how-to-customize-branding#layout&quot;&gt;upload the custom CSS file&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;MITM Attack Visibility&lt;/h2&gt;
&lt;p&gt;When a detection is triggered, it will be displayed in the SlashID Console, showing you the MITM token tag, the domain that is proxying requests, and the number of visits (total page loads and unique visits based on the IP address):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/mitm-detection/mitm-detection-table.png&quot; alt=&quot;mitm setup&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you want to be notified of MITM attacks as they happen, you can use SlashID &lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/webhooks/introduction&quot;&gt;webhooks&lt;/a&gt;. Any time a website with a SlashID MITM token is accessed via a malicious proxy, your webhook will receive an event with the following information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;IP address&lt;/em&gt; of the user accessing the page&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;user-agent&lt;/em&gt; string&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;attack domain&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;allowed domains&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;token tag&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Early detection allows you to respond promptly by notifying the affected parties, blocking the malicious proxy, or implementing additional security measures that prevent further compromise.&lt;/p&gt;
&lt;h2&gt;Try it out!&lt;/h2&gt;
&lt;p&gt;Protect your website against Man-in-the-Middle attacks by &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=mitm-bp&quot;&gt;signing up&lt;/a&gt; for a free SlashID account and following the simple steps outlined in this blog post.&lt;/p&gt;
</content:encoded><author>Ivan Kovic, Vincenzo Iozzo</author></item><item><title>ODPR: A Framework for Securing Non-Human Identities</title><link>https://slashid.dev/blog/nhi-security/</link><guid isPermaLink="true">https://slashid.dev/blog/nhi-security/</guid><description>Identity-based attacks have become the primary way attackers move laterally in a network. They are also responsible for half of the initial intrusions.  Addressing these attacks requires very different tooling and approaches compared to malware-based attacks.  In this article we delve into the SlashID framework to address these attacks.</description><pubDate>Mon, 17 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;ODPR: A Framework for Securing Non-Human Identities&lt;/h2&gt;
&lt;p&gt;Over the last few months, there have been a number of newsworthy data breaches stemming from compromised session tokens, API keys, service accounts, secrets - digital credentials and permissions for automated systems now referred to as non-human identities (NHIs)&lt;/p&gt;
&lt;p&gt;Cloudflare, Dropbox, Microsoft, and most recently Hugging Face have all been breached recently due to NHI misuse. While user identities have received plenty of protections in place such as multi-factor authentication, step-up authentication, SSO, and a number of different access control &amp;amp; authorization policies, NHIs simply don’t have the same protections and rigorous hygiene in place.&lt;/p&gt;
&lt;p&gt;Years ago, CrowdStrike created the 1-10-60 framework for incident response: 1 minute to detect, 10 to investigate, and 60 minutes to isolate or remediate the incident. When it comes to identity-based threats, we lack the tools to protect companies before the attacker breaks out.&lt;/p&gt;
&lt;p&gt;To help companies protect these highly-permissioned assets, SlashID has created a simple framework to follow: Observe, Detect, Prevent, &amp;amp; Remediate.&lt;/p&gt;
&lt;h2&gt;Observe&lt;/h2&gt;
&lt;p&gt;The surge in non-human identities (NHIs) has been driven by several factors, including the use of Kubernetes clusters and containers, the adoption of microservices, the integration of cloud services and automation, and the widespread use of third-party SaaS applications by organizations.
Non-human identities outnumber humans as much as 50 to 1 at most enterprise companies. They’re created and used across a complex web of applications, APIs, and workloads owned by several different teams. As a result, companies struggle to piece together a complete view of these highly-permissioned assets, leaving them vulnerable to misuse.&lt;/p&gt;
&lt;p&gt;Building a complete NHI inventory across cloud vendors, including over-privileged credentials and transitive trust relationships is the foundational piece of this framework. Centralizing visibility over NHIs not only removes uncertainty but also enables a standardized approach for all stakeholders - security teams, developers, risk &amp;amp; compliance - to follow rather than working across several different tools and platforms to complete their work.&lt;/p&gt;
&lt;h2&gt;Detect&lt;/h2&gt;
&lt;p&gt;Just because you now have visibility over all of your NHIs doesn’t mean they’re secure. Detecting threats in real time is the next piece of the framework. Static detection based on manual or periodic scans is no longer enough to prevent NHI misuse. Security teams need to be able to respond to threats quickly to reduce the mean time to detect and respond, and static detection leaves windows for threats to go undetected where a lot of damage can be done.&lt;/p&gt;
&lt;p&gt;Additionally, not all threats are equal. It is important to be able to prioritize threats based on severity so security teams can act accordingly. SlashID detects threats against your NHI and allows you to customize their severity score based on your unique environment. The alerts can be consumed via a SIEM, email, slack, or custom notifications.&lt;/p&gt;
&lt;h2&gt;Prevent&lt;/h2&gt;
&lt;p&gt;Once you have complete visibility over your NHIs and the ability to detect threats in real time, the next stage of the framework is prevention. These efforts are focused on minimizing the surface handling key material and enforcing fine-grained authorization and least privilege access.&lt;/p&gt;
&lt;p&gt;Starting with minimizing the surface handling key material, we recommend credential tokenization. By keeping secrets out of application code and configuration files, it reduces the risk of accidental exposure or deliberate theft of credentials.&lt;/p&gt;
&lt;p&gt;After preventing accidental exposure of credentials, adopting OAuth2 scopes for fine-grained access control ensures that NHIs have only the necessary permissions for their tasks. This approach minimizes the risk of over-privileged credentials being misused.&lt;/p&gt;
&lt;p&gt;Finally, enforcing conditional access controls minimizes the chances of unauthorized access by ensuring that only known-good systems have access to sensitive data.&lt;/p&gt;
&lt;h2&gt;Remediate&lt;/h2&gt;
&lt;p&gt;The first three steps of the ODPR framework should be viewed as layers of security to prevent a breach. However, it is necessary to “Assume Breach” and plan for that scenario: rotate or revoke stolen credentials and contain the blast radius at runtime. Remediation is a significant challenge for NHI, even for the most advanced companies. Consider Cloudflare’s November 2023 breach - responding to an initial breach, they missed rotating just 1 access token and 3 service account credentials out of thousands and as a result, hackers were able to access their entire Atlassian suite and a Bitbucket service account allowing access to the source code management system.&lt;/p&gt;
&lt;p&gt;Remediation for NHI is about 3 core questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Can you rotate credentials quickly without downtime?&lt;/li&gt;
&lt;li&gt;Can you drop a credential privilege before the attacker can move laterally?&lt;/li&gt;
&lt;li&gt;Can you verify the attacker hasn’t achieved persistence in your environment?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The ODPR framework—Observe, Detect, Prevent, and Remediate—provides a robust strategy for securing non-human identities (NHIs) in complex digital environments. By ensuring comprehensive visibility, real-time threat detection, fine-grained authorization, and rapid remediation, organizations can mitigate the risks of data breaches. As NHIs continue to grow in number, implementing these measures is crucial for protecting critical assets and enhancing overall security. Adopting the ODPR framework helps companies maintain a strong defense against evolving threats.&lt;/p&gt;
</content:encoded><author>Ben Reinheimer, Vincenzo Iozzo</author></item><item><title>Non-Human Identities Security: Breaking down the problem</title><link>https://slashid.dev/blog/non-human-identity-security/</link><guid isPermaLink="true">https://slashid.dev/blog/non-human-identity-security/</guid><description>Compromised non-human identities are increasingly being leveraged by attackers to gain initial access and as a vector for lateral movement.  Microsoft, Cloudflare, and Dropbox are just a few of the companies that have fallen victim to this growing threat this year.  In this blog post, we focus on the attack vectors involved and on what actions companies must take to prevent these attacks.</description><pubDate>Mon, 16 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Fueled by several NHI-related breaches, NHI security companies (SlashID included) are everywhere these days - but, what are the core issues, and what’s causing them?&lt;/p&gt;
&lt;p&gt;In this blog post, we seek to map out the problem and draw some lessons from application security, endpoint, and other areas of security to reason about the problem.&lt;/p&gt;
&lt;h2&gt;Attack patterns against NHI&lt;/h2&gt;
&lt;p&gt;The MITRE ATT&amp;amp;CK framework is very useful for mapping attack patterns against NHIs:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/nhi-security/mitre-attack.png&quot; alt=&quot;mitre&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Several of the techniques discussed here apply to NHIs - in particular, limiting this to &lt;code&gt;Initial Access&lt;/code&gt; and &lt;code&gt;Credential Access&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Trusted relationships&lt;/li&gt;
&lt;li&gt;Credentials from password stores&lt;/li&gt;
&lt;li&gt;Exploitation for Credential Access&lt;/li&gt;
&lt;li&gt;Steal application access token&lt;/li&gt;
&lt;li&gt;Steal or Forge Authentication Certificates&lt;/li&gt;
&lt;li&gt;Steal Web Session Cookies&lt;/li&gt;
&lt;li&gt;Unsecured Credentials&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In practice, looking at historical data we see adversaries perform the following techniques to gain access via NHIs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;OAuth social engineering (notable adversary: &lt;a href=&quot;https://attack.mitre.org/groups/G0007/&quot;&gt;APT28/GRU&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Stolen, lost, or forged credentials (notable adversary: &lt;a href=&quot;https://attack.mitre.org/groups/G0016/&quot;&gt;APT29/SVR&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;An important sub-category of (2) is Kerberos/Active Directory-related token forging or stealing (notable adversary: &lt;a href=&quot;https://attack.mitre.org/groups/G0010/&quot;&gt;Turla/FSB&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Exploitation of federation trust relationships (notable adversary: &lt;a href=&quot;https://attack.mitre.org/groups/G1015/&quot;&gt;Scattered Spider&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In this article we are not going to focus on OAuth social engineering and the exploitation of federation trust relationships for two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;OAuth social engineering is a broader problem that applies to user accounts as well. Similarly, federated trusts relationships broadly apply to user identities. We are going to cover both in separate posts&lt;/li&gt;
&lt;li&gt;While both techniques are dangerous, stolen, forged and lost credentials represent the bulk of real-world intrusions by a significant margin&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;What is unique about NHIs?&lt;/h2&gt;
&lt;p&gt;With a taxonomy of attacks under our belt, we can reason about the unique challenges associated with NHIs. At a high level, we see 5:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You can’t enforce Multi-Factor Authentication (MFA)&lt;/li&gt;
&lt;li&gt;Their credentials can’t be rotated easily&lt;/li&gt;
&lt;li&gt;Lifecycle management is lacking: it is hard to know who has access to them, and how and when they should be de-provisioned. Further, there’s no source of truth/” known good” for them&lt;/li&gt;
&lt;li&gt;There are too many of them and their usage is not self-explanatory&lt;/li&gt;
&lt;li&gt;They are often over-privileged&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result is that, as an industry, we face the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Attackers target NHIs because they can log into a system and move laterally much more easily&lt;/li&gt;
&lt;li&gt;If there is a breach or suspected breach, the time to remediate is extremely long&lt;/li&gt;
&lt;li&gt;It’s hard to stay compliant&lt;/li&gt;
&lt;li&gt;Because NHIs are so often overprivileged, privilege escalation and lateral movement are much easier to achieve&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Analyzing the problem&lt;/h2&gt;
&lt;p&gt;In summary, we are facing two core issues.&lt;/p&gt;
&lt;p&gt;One is that NHIs make for perfect candidates for credential-based attacks, and the second is that governance of these identities is too hard due to sprawl and lack of granular visibility.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When we talk about governance in this context we mean both lifecycle management as well as entitlement management.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dissecting the problem further leads us to several questions.&lt;/p&gt;
&lt;h3&gt;Are NHI issues a misnomer? Are they credential issues?&lt;/h3&gt;
&lt;p&gt;While not all NHI issues are credentials-related, without credentials, we could be way less concerned about the remaining NHI attack vectors.&lt;/p&gt;
&lt;p&gt;Specifically, without credentials, NHIs would be a significantly less popular attack vector because there would be a lot fewer ways to impersonate an NHI - in particular, phishing, endpoint/workload breach, and trusted relationships exploitation.&lt;/p&gt;
&lt;p&gt;As we can see from past breaches the bulk of identity attacks are credential-related, just a few examples:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The recently branded UnOAuthorized &lt;a href=&quot;https://www.semperis.com/blog/unoauthorized-privilege-elevation-through-microsoft-applications/&quot;&gt;attack&lt;/a&gt; against Microsoft apps involves credentials attached to service principals and then misusing these credentials for persistence and privilege escalation&lt;/li&gt;
&lt;li&gt;The AWS credential leak campaign &lt;a href=&quot;https://unit42.paloaltonetworks.com/large-scale-cloud-extortion-operation/&quot;&gt;reported&lt;/a&gt; by Palo Alto Unit 42 is another example of NHI credentials abused&lt;/li&gt;
&lt;li&gt;One of the most &lt;a href=&quot;https://hackingthe.cloud/aws/exploitation/ec2-metadata-ssrf/&quot;&gt;infamous attack vectors&lt;/a&gt; for AWS historically were Server-Side Request Forgery (SSRF) attacks to steal EC2 metadata credentials.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So yes, many NHI concerns are credential concerns in disguise.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;While it&apos;s not feasible to get rid of credentials altogether, short-lived credentials can greatly reduce risk and increase the ability to generate high signal detections.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Would workload-bound credentials solve the problem?&lt;/h3&gt;
&lt;p&gt;As we have discovered, one of the central issues with NHIs is that they are privileged, and impersonating one by stealing credentials is relatively easy.&lt;/p&gt;
&lt;p&gt;Workload-bound credentials would significantly reduce the number of attacks you could carry via an NHI. If you can safely attest a device or a workload and protect its identity we would make the value of an NHI limited in that it could only be misused while maintaining persistence on the workload using it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;However, as we&apos;ve seen in application security, even the most locked-down applications still get compromised. Locking down credentials increases the complexity of the attack but by no means solves the problem entirely.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;When it comes to governance, are lifecycle management and over-privileged identities a solvable problem?&lt;/h3&gt;
&lt;p&gt;In a perfect world access issuance would be significantly down-scoped to just the number of permissions necessary for that NHI to perform its role. A good parallel to think about is sandboxing applications on desktops.&lt;/p&gt;
&lt;p&gt;Sandboxing has been incredibly useful in reducing the odds of privilege escalation once an attacker has compromised the application. However, it is also incredibly difficult to apply in real life.&lt;/p&gt;
&lt;p&gt;Most applications are still far from being sandboxed and it took Chrome several years and an inordinate amount of engineering effort.&lt;/p&gt;
&lt;p&gt;If the parallel holds we can then conclude that achieving perfect least privilege won’t be feasible and we’ll have to accept a trade-off, however, it is equally true that the current state of NHI privilege management is similar to applications before sandboxing was widely used.&lt;/p&gt;
&lt;h3&gt;Is this an identity, engineering, or SOC issue?&lt;/h3&gt;
&lt;p&gt;We believe the answer is all of the above and a unified approach decreases the odds of a successful breach.&lt;/p&gt;
&lt;h2&gt;The ideal solution&lt;/h2&gt;
&lt;p&gt;The NHI problem is not fundamentally new - in fact, as an industry, we&apos;ve had many similar challenges in the past and, as usual in security, a layered approach is the right way to think about it. In particular:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Short-lived credentials&lt;/li&gt;
&lt;li&gt;Device/workload-bound identities&lt;/li&gt;
&lt;li&gt;Down-scoped privileges for each identity&lt;/li&gt;
&lt;li&gt;Governance around provisioning, de-provisioning, and federated trust&lt;/li&gt;
&lt;li&gt;A detection and response framework to stop the remaining potential breaches&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It is key to remember that even if 1-4 are in place, the fundamental threat still exists and is unavoidable. When we think about it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Breaching a workload or a user will still happen and it will likely still result in the loss of key material later leveraged for lateral movement&lt;/li&gt;
&lt;li&gt;Phishing via OAuth apps and federation can be abused to pivot from a user account to an NHI&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So it&apos;s clear that the three pillars to solve this problem are engineering, governance, and detection and response.&lt;/p&gt;
&lt;h2&gt;How close are we to that?&lt;/h2&gt;
&lt;p&gt;While there are several standards out there and, to some degree, practical implementations of device-bound tokens and ephemeral credentials, in practice, several problems emerge:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Most applications don’t support ephemeral credentials or even multiple credentials per tenant/account. As a result, credential rotation and revocation are risky and could result in downtime. Furthermore, several applications require a user to login through the UI to generate a new set of credentials making automation harder&lt;/li&gt;
&lt;li&gt;Workload/device attestation is feasible but not trivial to solve at scale&lt;/li&gt;
&lt;li&gt;Least privilege, as we discussed, is hard to achieve. For instance, AWS has 15,561 API methods and 17,181 permissions&lt;/li&gt;
&lt;li&gt;Sometimes NHIs are used outside of workloads so attestation is not an option to begin with&lt;/li&gt;
&lt;li&gt;Lastly a number of attack paths are not easily addressable and have never been historically, e.g.: phishing and exploitation of application vulnerabilities&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;So what can we practically do today?&lt;/h2&gt;
&lt;p&gt;Even though we can’t easily get to the ideal solution, several practical improvements are feasible today:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a vaulting solution to reduce the risk of credential sprawl&lt;/li&gt;
&lt;li&gt;Adopt a secret scanner to reduce the risk of accidental credential leaks&lt;/li&gt;
&lt;li&gt;Tokenize credentials/adopt a secret-less posture. The basic premise is to minimize the surface-touching key material to make it easier to perform rotation, reduce the odds of needing to rotate credentials in the first place and generate an audit trail on usage&lt;/li&gt;
&lt;li&gt;Implement and enforce conditional access for NHIs&lt;/li&gt;
&lt;li&gt;Unify observability and inventory of the various NHIs present across your environments&lt;/li&gt;
&lt;li&gt;Detect and automatically respond - put a strong detection and response framework in place to reduce the risk of a lost or stolen credential or a compromised identity.
According to CrowdStrike, attackers break out in approximately 30 minutes - it&apos;s important to react automatically to reduce the blast radius of the incident.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How can SlashID help?&lt;/h2&gt;
&lt;p&gt;SlashID can help in three ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You can adopt our platform to observe, detect, and automatically respond to NHI breaches. You can read more about our approach &lt;a href=&quot;https://slashid.com/blog/large-scale-env-files-breach/&quot;&gt;here&lt;/a&gt; based on the Palo Alto Unit 42 report of the latest AWS credential theft campaign&lt;/li&gt;
&lt;li&gt;You can adopt our Gate module to implement credential tokenization and conditional access for NHIs. This is an overview of how we approach &lt;a href=&quot;https://slashid.com/blog/credential-tokenization/&quot;&gt;tokenization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Provide visibility over trust relationships, privileges, and remediation capabilities to help solve the governance issue&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We hope this blog post helped frame the issues surrounding NHIs and some of the approaches to solving them.&lt;/p&gt;
&lt;p&gt;Ultimately the perfect solution requires a significant engineering effort from both vendors and consumers of a service, making it hard to achieve in the real world in the short term.&lt;/p&gt;
&lt;p&gt;Until then, we believe that security teams should put in place a credential minimization strategy and a detection and response framework.&lt;/p&gt;
&lt;p&gt;Reach out to us to let us know how you are approaching the problem or to schedule a free &lt;a href=&quot;https://www.slashid.dev/contact/identity-security-assessment/&quot;&gt;Identity Security Assessment&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>NYDFS 2026 Vishing Advisory: Detection and Defense with SlashID</title><link>https://slashid.dev/blog/nydfs-vishing-guidance/</link><guid isPermaLink="true">https://slashid.dev/blog/nydfs-vishing-guidance/</guid><description>On February 6, 2026, NYDFS issued an industry letter warning DFS-regulated entities about a spike in targeted vishing attacks where threat actors impersonate IT help desk staff to steal credentials and MFA codes.  This post breaks down the technical mechanics of the campaign, why it reliably bypasses MFA, and how to detect and contain it using identity telemetry with SlashID Identity Protection, Mutual TOTP verification, and MITM detection.</description><pubDate>Fri, 06 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On February 6, 2026, NYDFS issued an industry letter warning DFS-regulated entities about a spike in targeted vishing attacks where threat actors impersonate IT help desk staff to steal credentials and MFA codes and gain remote access.&lt;/p&gt;
&lt;p&gt;This post breaks down the technical mechanics of the campaign, why it reliably bypasses &quot;MFA on paper,&quot; and how to detect and contain it using identity telemetry -- specifically mapping the NYDFS recommendations to SlashID Identity Protection, and MITM detection.&lt;/p&gt;
&lt;h2&gt;1. Attack anatomy: why this vishing flow works?&lt;/h2&gt;
&lt;p&gt;NYDFS describes a consistent pattern: attackers call employees on work/personal numbers (&lt;strong&gt;Impersonation&lt;/strong&gt;), direct them to a malicious link (&lt;strong&gt;Social Engineering&lt;/strong&gt;), &lt;strong&gt;capture credentials&lt;/strong&gt; in real time, and then prompt for the MFA code to immediately authenticate and obtain remote access.&lt;/p&gt;
&lt;h3&gt;A more technical view of the kill chain&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Phase A --&lt;/strong&gt; After the reconnaissance phase, where enterprise information is gathered and enumerated, attackers attempt to gain an &lt;strong&gt;initial foothold&lt;/strong&gt; by authenticating via out-of-band channels using various techniques like Help Desk Impersonation, SMS and vishing, and SIM swapping.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase B&lt;/strong&gt; -- &lt;strong&gt;Foothold Establishment --&lt;/strong&gt; by using compromised credentials, and SSO access using a legitimate account (potentially enabling lateral movement across the environment).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase C&lt;/strong&gt; -- &lt;strong&gt;MFA bypass&lt;/strong&gt;. The expression &quot;&lt;em&gt;Attackers don&apos;t hack, they log in&lt;/em&gt;&quot; is becoming more and more relevant. Even strong MFA can be defeated indirectly if the attacker can force account recovery, enroll a new device, or abuse help desk reset processes. The root cause of MFA bypass is the ability to &lt;strong&gt;replay tokens (SAML, OAuth)&lt;/strong&gt; &lt;strong&gt;before they are expired.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/nydfs-vishing-guidance/image1.png&quot; alt=&quot;NYDFS Vishing Attack Flow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It seems that the same attack vector SlashID previously analyzed in the &lt;a href=&quot;https://www.slashid.dev/blog/scattered-spider-tradecraft/&quot;&gt;Scattered Spider breaches&lt;/a&gt; at MGM and Caesars is not only still relevant today, but has become even more sophisticated.&lt;/p&gt;
&lt;h2&gt;2. How SlashID helps to detect and prevent&lt;/h2&gt;
&lt;p&gt;A vishing compromise leaves a very &quot;identity-shaped&quot; trail. The most actionable signals are not the phone call - they&apos;re the sequence of identity events. High-signal detection patterns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Password reset → new login from new geo/device within minutes&lt;/li&gt;
&lt;li&gt;New MFA device enrolled shortly after reset&lt;/li&gt;
&lt;li&gt;SSO session minted from a new IP / ASN, then immediate access to high-value apps&lt;/li&gt;
&lt;li&gt;Privilege escalation attempts after initial foothold&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SlashID is structured around two main ideas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ingest identity posture + event logs from the sources (IdP, cloud, AD/Entra, SaaS), and&lt;/li&gt;
&lt;li&gt;Run a detection engine that emits threat detections and posture detections.&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attack Phase&lt;/th&gt;
&lt;th&gt;How the Attacker Exploits&lt;/th&gt;
&lt;th&gt;How SlashID Detects&lt;/th&gt;
&lt;th&gt;How SlashID Prevents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Recon &amp;amp; Initial Foothold&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Impersonates IT Help Desk via phone call (vishing) or SMS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;N/A&lt;/strong&gt; (The phone call itself occurs outside the identity plane).&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Browser Extension + TOTP Verification&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Credential Harvesting &amp;amp; MFA Interception&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User visits a fake login page (often a reverse proxy like Evilginx). Attacker captures credentials and the MFA code as the user types them.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MITM Token Detection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TOTP Verification&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lateral Movement &amp;amp; Impact&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;With a valid SSO session, the attacker accesses file shares, or internal tools to exfiltrate data.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Identity Graph Analysis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;AI Assistant &amp;amp; Queries&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These detection and response methods are discussed in more details in the following chapters.&lt;/p&gt;
&lt;h3&gt;2.1. Visibility Foundations and Detection Types that match vishing outcomes&lt;/h3&gt;
&lt;p&gt;SlashID typically ingests two types of data from each integrated data source such as &lt;strong&gt;Identity Data&lt;/strong&gt; (users, NHIs, roles, permissions) and &lt;strong&gt;Events&lt;/strong&gt; (activity logs showing how identities are used).&lt;/p&gt;
&lt;p&gt;On the other hand, SlashID detection events include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Threat detection&lt;/strong&gt; — Indicators of attack (IOAs)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Posture detection&lt;/strong&gt; — Misconfigurations (e.g., MFA disabled)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detection functions&lt;/strong&gt; — CredentialStuffingAttempt, FederationBypass, MissingMFA&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Confidence score&lt;/strong&gt; — Machine Learning model with a value between 0 and 1 indicating the likelihood that the detection is a true positive&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MITRE mapping fields&lt;/strong&gt; — Automatic mapping to MITRE ATT&amp;amp;CK framework&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Thus, providing continuous monitoring and detection.&lt;/p&gt;
&lt;h3&gt;2.2. Mutual TOTP for Help Desk vishing&lt;/h3&gt;
&lt;p&gt;The defining weakness of help desk vishing is that the call itself becomes the &quot;authentication channel&quot; as shown in the table above. Attackers win by convincing an agent to reset credentials / enroll MFA / approve recovery without a cryptographic way to verify who is on the other end of the line.&lt;/p&gt;
&lt;p&gt;Mutual TOTP changes that: instead of trusting a voice, both parties perform a two-way verification handshake where each side must prove possession of a registered device (the verification is biometric-protected). It uses 6-digit RFC 6238 codes, rotating every 30 seconds, with bidirectional verification, real-time synchronization, and a 2-minute timeout.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/nydfs-vishing-guidance/image3.png&quot; alt=&quot;Mutual TOTP Verification Flow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Operational model:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Employee and help desk agent are both enrolled&lt;/strong&gt; (activated with an IT token; access protected by FaceID/TouchID).&lt;/li&gt;
&lt;li&gt;One party initiates a &lt;strong&gt;handshake&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Both sides receive &lt;strong&gt;unique 6-digit TOTPs&lt;/strong&gt; and read them to each other over the call.&lt;/li&gt;
&lt;li&gt;The handshake is marked &lt;strong&gt;Verified&lt;/strong&gt; only when &lt;strong&gt;both&lt;/strong&gt; codes are confirmed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Mutual TOTP blocks the &quot;operator-assisted MFA bypass&quot; moves because the attacker cannot complete the handshake without the registered device -- thus, &lt;strong&gt;voice cloning/deepfakes won&apos;t help&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;2.3. Browser extension for phishing detection&lt;/h3&gt;
&lt;p&gt;SlashID also provides a browser extension that performs real-time phishing detection and identity protection at the client layer. While mutual TOTP secures the phone call, the browser extension secures the link click -- the other critical hinge point in vishing campaigns.&lt;/p&gt;
&lt;p&gt;SlashID&apos;s browser extension is positioned to stop phishing where it happens: in the browser, before credentials are submitted. It inspects pages as they load by evaluating DOM, URL reputation, and form behavior, then a policy layer makes a decision (brand impersonation, typosquats, risky flows), and the extension can warn/block submission, or trigger extra authentication.&lt;/p&gt;
&lt;h3&gt;2.4. Detecting fake login attempts and MITM phishing&lt;/h3&gt;
&lt;p&gt;The vishing flow depends on redirecting victims to a malicious login page. SlashID provides an explicit MITM (reverse proxies) detection mechanism: you generate a MITM token tied to allowed domains, embed it in pages, and SlashID can detect when login pages are being accessed via unauthorized domains/proxy contexts. This is a strong fit for SSO login pages such as Okta or Entra.&lt;/p&gt;
&lt;h3&gt;2.5. Query the identity graph to confirm blast radius quickly&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;SlashID Identity Protection&lt;/strong&gt; exposes your identity environment as a graph (identities, credentials, groups, policies, resources, and the relationships between them). By leveraging Cypher queries, security teams can quickly answer who has access to what, how did they get it, and what changed recently -- which is exactly what you need during incidents like help desk vishing.&lt;/p&gt;
&lt;p&gt;SlashID implements some high-value quality query patterns used to identify overprivileged identities, detect privilege escalation attempts, discover NHIs with unrestricted access to resources, and analyzing blast radius by identifying federated access risks.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/nydfs-vishing-guidance/image2.png&quot; alt=&quot;SlashID Identity Graph Analysis&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For deeper investigations, analysts can leverage SlashID&apos;s &lt;strong&gt;AI assistant&lt;/strong&gt; to generate &lt;strong&gt;natural-language queries&lt;/strong&gt; that surface risky access patterns, privilege escalation, or lateral movement paths.&lt;/p&gt;
&lt;h2&gt;3. A concrete NYDFS-aligned control map using SlashID&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.dfs.ny.gov/industry-guidance/industry-letters/20260206-cybersecurity-advisory-targeted-vishing-attacks&quot;&gt;NYDFS latest industry letter&lt;/a&gt; guidance focuses on preventing credential theft, strengthening MFA controls, and monitoring identity activity for signs of compromise. SlashID maps directly to these requirements by combining identity telemetry, phishing detection, and automated remediation to detect and contain vishing-driven account takeovers.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;NYDFS Control Requirement&lt;/th&gt;
&lt;th&gt;How SlashID Helps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Identity Verification Procedures (not caller ID)&lt;/td&gt;
&lt;td&gt;Monitor password reset and MFA enrollment events; trigger detection webhooks for suspicious identity changes and automate containment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strengthen MFA controls and enrollment permissions&lt;/td&gt;
&lt;td&gt;Detect MFA posture gaps (e.g., MissingMFA), enforce stronger authentication workflows, and enable SlashID&apos;s &lt;strong&gt;TOTP&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continuous monitoring of authentication activity&lt;/td&gt;
&lt;td&gt;Correlate identity telemetry across sources and query the identity graph to identify privilege escalation or unusual access patterns (e.g., FederationBypass)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MFA must be continuously evaluated&lt;/td&gt;
&lt;td&gt;Use posture detections and continuous assessments to validate MFA coverage and flag configuration drift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reduce exposure to fake login portals&lt;/td&gt;
&lt;td&gt;Deploy MITM token detection on authentication surfaces to detect malicious login proxies and phishing infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Help desk vishing attacks show how modern intrusions increasingly target identity workflows rather than technical vulnerabilities. By exploiting social engineering and weak identity verification processes, attackers can obtain legitimate access and bypass traditional defenses.&lt;/p&gt;
&lt;p&gt;SlashID helps break this attack chain by combining identity telemetry, behavioral detections, phishing protection, and identity graph analysis to detect suspicious authentication patterns and quickly contain compromised accounts. By implementing identity-centric security controls with SlashID, organizations can significantly reduce the risk of vishing-driven account takeovers and stop attackers before they can expand their foothold.&lt;/p&gt;
&lt;p&gt;If you&apos;re looking to strengthen your defenses against vishing attacks, &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;contact SlashID&lt;/a&gt; to learn how our platform can help protect your organization.&lt;/p&gt;
</content:encoded><author>SlashID Team, Vincenzo Iozzo</author></item><item><title>Protecting against malicious OAuth 2.0 applications</title><link>https://slashid.dev/blog/oauth-chrome-extension-breach/</link><guid isPermaLink="true">https://slashid.dev/blog/oauth-chrome-extension-breach/</guid><description>Several Chrome extension developers were compromised in recent weeks by an attack seeking to create a backdoor in the extensions.  The root cause of the breach was a phishing email that leveraged OAuth 2.0/OIDC to steal the user credentials.  This blog post explores the details of such attacks and how SlashID can help detect them and contain the blast radius.</description><pubDate>Wed, 08 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;OAuth 2.0 apps are back under the spotlight following the recent breach that affected several &lt;a href=&quot;https://www.reuters.com/technology/cybersecurity/data-loss-prevention-company-cyberhaven-hit-by-breach-statement-says-2024-12-27/&quot;&gt;vendors&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In particular, the attackers created a rouge OAuth 2.0 app to phish a developer and obtain their credentials for Google.
Far from a one-time instance, several attacks of this kind have been observed through the &lt;a href=&quot;https://www.microsoft.com/en-us/security/blog/2023/12/12/threat-actors-misuse-oauth-applications-to-automate-financially-driven-attacks/&quot;&gt;years&lt;/a&gt; primarily executed by national state actors like APT29.&lt;/p&gt;
&lt;p&gt;An OAuth 2.0 application leverages OAuth 2.0 to obtain authorization on behalf of a user to access a service or a resource.&lt;/p&gt;
&lt;p&gt;From a user standpoint, the phishing attempt looks genuine and it&apos;s hard to distinguish from a legitimate application. In the video below you can see such a flow in action:&lt;/p&gt;
&lt;p&gt;&lt;video style=&quot;width:100%&quot; autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/blog/chrome-extension-breach/flow.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&gt;

&lt;/p&gt;
&lt;p&gt;In this example, once the user grants access to their account, the attacker can leverage the auth token to upload malicious code to the Google Store.&lt;/p&gt;
&lt;p&gt;Let&apos;s dig deeper to understand how these attacks are carried out.&lt;/p&gt;
&lt;h2&gt;OAuth 2.0 and OIDC authorization at a high level&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Feel free to &lt;a href=&quot;#abusing-trust&quot;&gt;skip&lt;/a&gt; this section if you are already familiar with OAuth 2.0 and OIDC&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It would take too long to describe all the possible OAuth 2.0 flows and they are generally well documented online. To summarize in a picture:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/oauth2-security/flow.png&quot; alt=&quot;OAuth 2.0 flow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At a high level, here is some contextual information useful for the rest of the article:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Roles&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Resource Owner&lt;/strong&gt;: The user who authorizes an application to access their account.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource Server&lt;/strong&gt;: The server hosting the user data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client&lt;/strong&gt;: The application that wants to access the user&apos;s account.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorization Server&lt;/strong&gt;: The server that authenticates the resource owner and issues access tokens to the client.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flow&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;client&lt;/strong&gt; requests authorization from the &lt;strong&gt;resource owner&lt;/strong&gt; to access their resources.&lt;/li&gt;
&lt;li&gt;If the &lt;strong&gt;resource owner&lt;/strong&gt; grants authorization, the &lt;strong&gt;client&lt;/strong&gt; receives an authorization grant, which is a credential representing the resource owner&apos;s authorization.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;client&lt;/strong&gt; requests an access token from the &lt;strong&gt;authorization server&lt;/strong&gt; by presenting the authorization grant and authentication.&lt;/li&gt;
&lt;li&gt;If the request is valid, the &lt;strong&gt;authorization server&lt;/strong&gt; issues an access token to the &lt;strong&gt;client&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;client&lt;/strong&gt; uses the access token to access the protected resources hosted by the &lt;strong&gt;resource server&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Authorization Grant Types&lt;/strong&gt;: OAuth 2.0 defines several grant types but in this article, we&apos;ll touch on the two most commonly used ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authorization Code&lt;/strong&gt;: Used with web applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implicit&lt;/strong&gt;: Simplified flow, mostly used by mobile or web applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access Token&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This token represents the authorization of a specific application to access specific parts of a user&apos;s data.&lt;/li&gt;
&lt;li&gt;The client must send this token to the resource server in every request.&lt;/li&gt;
&lt;li&gt;Tokens are limited in scope and duration.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As it is often explained publicly, OAuth 2.0 doesn&apos;t provide an authentication flow hence it is generally used in conjunction to OpenID Connect (OIDC). OIDC allows clients to verify the identity of an end-user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end-user.
At a high level:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Roles&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;End-User&lt;/strong&gt;: The individual whose identity is being verified.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client&lt;/strong&gt;: The application requiring the end-user&apos;s identity, typically a web app, mobile app, or server-side application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorization Server (OpenID Provider)&lt;/strong&gt;: The server that authenticates the end-user and provides identity tokens to the client.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flow&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;client&lt;/strong&gt; requests authorization from the &lt;strong&gt;end-user&lt;/strong&gt;. This is typically done through a user agent (like a web browser) and involves redirecting the user to the authorization server.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;authorization server&lt;/strong&gt; authenticates the &lt;strong&gt;end-user&lt;/strong&gt;. This may involve the user logging in with a username and password or other authentication methods.&lt;/li&gt;
&lt;li&gt;Once authenticated, the &lt;strong&gt;end-user&lt;/strong&gt; is asked to grant permission for the client to access their identity information.&lt;/li&gt;
&lt;li&gt;After the end-user grants permission, the &lt;strong&gt;authorization server&lt;/strong&gt; redirects back to the &lt;strong&gt;client&lt;/strong&gt; with an ID Token and, often, an Access Token.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;ID Token&lt;/strong&gt; is a JSON Web Token (JWT) that contains claims about the identity of the user. The client will validate this token to ensure it&apos;s authentic.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Access Token&lt;/strong&gt; can be used by the client to access the user&apos;s profile information via a user-info endpoint on the authorization server.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scopes and Claims&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;During the authorization request, the client specifies the scope of the access it is requesting. Common scopes in OpenID Connect include &lt;code&gt;openid&lt;/code&gt; (required), &lt;code&gt;profile&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Claims are pieces of information about the user, such as the user&apos;s name, email, and so forth. These are included in the ID Token based on the requested scopes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In practice, at a high level, a client is given a &lt;code&gt;client_id&lt;/code&gt; and a &lt;code&gt;client_secret&lt;/code&gt;. The client initiates a request sending those two parameters among others to the authorization server, the authorization server first verifies the validity of the &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; and then proceeds to verify the user. Once that&apos;s done, the user is redirected to a &lt;code&gt;redirect_uri&lt;/code&gt; passed to the authorization server by the client. From there on, depending on the OAuth 2.0 flow, there might be further exchanges between the client and the authorization server server.&lt;/p&gt;
&lt;h2&gt;Abusing trust&lt;/h2&gt;
&lt;p&gt;Once a user has been redirected to the app/Client that initiated the OAuth 2.0 the app can do two crucial things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Verify the identity of the user with respect to the ownership of a given identifier (eg: an email address)&lt;/li&gt;
&lt;li&gt;Access resources on behalf of the user based on the scopes that the user granted to the app&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both could potentially be abused by an attacker.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Both Google and Microsoft have verification processes in place to verify that an OAuth 2.0 app is legitimate, however several caveats apply. In practice, there are several ways for attackers to bypass the verification process or get around it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For the former, an attacker could, under certain circumstances, impersonate the user with a third-party service. See an example &lt;a href=&quot;https://salt.security/blog/oh-auth-abusing-oauth-to-take-over-millions-of-accounts?&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, the most recent breach leveraged this capability to deploy a backdoored version of the companies&apos; Chrome extensions by requesting the scopes required to publish updates to the Chrome Web Store (&lt;code&gt;https://www.googleapis.com/auth/chromewebstore&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Note that, by using a similar process, an attacker could have performed other nefarious actions such as exfiltrating or deleting emails and files.&lt;/p&gt;
&lt;p&gt;Further, it&apos;s important to note that while IdPs such as Microsoft, Google, and Okta are more frequently targeted - the same potential issues apply to any provider that implements OAuth 2.0.&lt;/p&gt;
&lt;h2&gt;Let&apos;s see an example in practice&lt;/h2&gt;
&lt;p&gt;Replicating the Chrome extension attack is fairly trivial. Create a new &lt;a href=&quot;https://developers.google.com/workspace/guides/get-started&quot;&gt;OAuth 2.0 app&lt;/a&gt; in Google Cloud. In the list of scopes, select &lt;code&gt;https://www.googleapis.com/auth/chromewebstore&lt;/code&gt; as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/chrome-extension-breach/permissions.png&quot; alt=&quot;Permissions&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once the application has been created, generate a client ID and secret pair.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/chrome-extension-breach/clientid.png&quot; alt=&quot;Permissions&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With the new credentials, you can now use an OAuth 2.0 Authorization Flow to obtain access to the user account. Here&apos;s an example app in action:&lt;/p&gt;
&lt;p&gt;&lt;video style=&quot;width:100%&quot; autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/blog/chrome-extension-breach/flow.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&gt;

&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;At this point the application has access to the user account and can impersonate it to upload a malicious Chrome extension.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the corresponding React component to replicate it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { useState, useEffect } from &apos;react&apos;
import { ReadonlyURLSearchParams } from &apos;next/navigation&apos;

interface OAuthAppProps {
  searchParams: ReadonlyURLSearchParams;
}

// JWT decode function remains the same
const decodeJWT = (token: string) =&amp;gt; {
  try {
    const base64Url = token.split(&apos;.&apos;)[1]
    const base64 = base64Url.replace(/-/g, &apos;+&apos;).replace(/_/g, &apos;/&apos;)
    const jsonPayload = decodeURIComponent(
      atob(base64)
        .split(&apos;&apos;)
        .map((c) =&amp;gt; &apos;%&apos; + (&apos;00&apos; + c.charCodeAt(0).toString(16)).slice(-2))
        .join(&apos;&apos;)
    )
    return JSON.parse(jsonPayload)
  } catch (error) {
    console.error(&apos;Error decoding JWT:&apos;, error)
    return null
  }
}

const OAuthApp = ({ searchParams }: OAuthAppProps) =&amp;gt; {
  const [clientId, setClientId] = useState(
    &apos;412876789490-99r6dgbje8rmeahheig85kf6p7enbskr.apps.googleusercontent.com&apos;
  )
  const [clientSecret, setClientSecret] = useState(&apos;SPX-oy_XxHZHdQBABCSAS&apos;)
  const [authEndpoint, setAuthEndpoint] = useState(
    &apos;https://accounts.google.com/o/oauth2/auth&apos;
  )
  const [tokenEndpoint, setTokenEndpoint] = useState(
    &apos;https://oauth2.googleapis.com/token&apos;
  )
  const [redirectUri, setRedirectUri] = useState(&apos;http://localhost:3000&apos;)
  const [scope, setScope] = useState(
    &apos;openid email profile https://www.googleapis.com/auth/chromewebstore&apos;
  )
  const [accessToken, setAccessToken] = useState(&apos;&apos;)
  const [idToken, setIdToken] = useState(&apos;&apos;)
  const [decodedToken, setDecodedToken] = useState &amp;lt; any &amp;gt; null
  const [error, setError] = useState(&apos;&apos;)

  // Rest of the logic remains the same
  const startOAuth = () =&amp;gt; {
    if (!clientId || !authEndpoint) {
      setError(&apos;Client ID and Authorization Endpoint are required&apos;)
      return
    }

    const authUrl = new URL(authEndpoint)
    authUrl.searchParams.append(&apos;client_id&apos;, clientId)
    authUrl.searchParams.append(&apos;response_type&apos;, &apos;code&apos;)
    authUrl.searchParams.append(&apos;redirect_uri&apos;, redirectUri)
    if (scope) {
      authUrl.searchParams.append(&apos;scope&apos;, scope)
    }
    authUrl.searchParams.append(&apos;access_type&apos;, &apos;offline&apos;)
    authUrl.searchParams.append(&apos;prompt&apos;, &apos;consent&apos;)

    window.location.href = authUrl.toString()
  }

  useEffect(() =&amp;gt; {
    const code = searchParams.get(&apos;code&apos;)
    const error = searchParams.get(&apos;error&apos;)

    if (error) {
      setError(`Authorization error: ${error}`)
      return
    }

    if (code &amp;amp;&amp;amp; tokenEndpoint &amp;amp;&amp;amp; clientId &amp;amp;&amp;amp; clientSecret) {
      const exchangeToken = async () =&amp;gt; {
        try {
          const response = await fetch(tokenEndpoint, {
            method: &apos;POST&apos;,
            headers: {
              &apos;Content-Type&apos;: &apos;application/x-www-form-urlencoded&apos;,
            },
            body: new URLSearchParams({
              grant_type: &apos;authorization_code&apos;,
              code,
              redirect_uri: redirectUri,
              client_id: clientId,
              client_secret: clientSecret,
            }),
          })

          const data = await response.json()
          if (data.access_token) {
            setAccessToken(data.access_token)
            setError(&apos;&apos;)
            console.log(&apos;Full token response:&apos;, data)

            if (data.id_token) {
              setIdToken(data.id_token)
              const decoded = decodeJWT(data.id_token)
              setDecodedToken(decoded)

              if (decoded) {
                const now = Math.floor(Date.now() / 1000)
                if (decoded.exp &amp;amp;&amp;amp; decoded.exp &amp;lt; now) {
                  setError(&apos;ID Token has expired&apos;)
                }
                if (decoded.aud !== clientId) {
                  setError(&apos;ID Token audience mismatch&apos;)
                }
              }
            }
          } else {
            setError(&apos;Failed to get access token&apos;)
            console.error(&apos;Token response without access_token:&apos;, data)
          }
        } catch (err) {
          setError(&apos;Error exchanging code for token&apos;)
          console.error(&apos;Token exchange error:&apos;, err)
        }
      }

      exchangeToken()
    }
  }, [searchParams, clientId, clientSecret, tokenEndpoint, redirectUri])

  return (
    &amp;lt;div className=&quot;max-w-2xl mx-auto p-4&quot;&amp;gt;
      &amp;lt;div className=&quot;bg-gray-50 shadow-lg rounded-lg p-6 border border-gray-200&quot;&amp;gt;
        &amp;lt;div className=&quot;mb-6&quot;&amp;gt;
          &amp;lt;h2 className=&quot;text-2xl font-bold mb-2 text-gray-800&quot;&amp;gt;
            Google OAuth 2.0 Authorization
          &amp;lt;/h2&amp;gt;
          &amp;lt;p className=&quot;text-gray-600&quot;&amp;gt;
            Credentials and endpoints are pre-filled for Google OAuth
          &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div className=&quot;space-y-4&quot;&amp;gt;
          &amp;lt;div&amp;gt;
            &amp;lt;label
              className=&quot;block text-sm font-medium mb-1 text-gray-700&quot;
              htmlFor=&quot;clientId&quot;
            &amp;gt;
              Client ID
            &amp;lt;/label&amp;gt;
            &amp;lt;input
              id=&quot;clientId&quot;
              type=&quot;text&quot;
              className=&quot;w-full p-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-400 focus:border-gray-400 bg-white&quot;
              value={clientId}
              onChange={(e) =&amp;gt; setClientId(e.target.value)}
            /&amp;gt;
          &amp;lt;/div&amp;gt;

          &amp;lt;div&amp;gt;
            &amp;lt;label
              className=&quot;block text-sm font-medium mb-1 text-gray-700&quot;
              htmlFor=&quot;clientSecret&quot;
            &amp;gt;
              Client Secret
            &amp;lt;/label&amp;gt;
            &amp;lt;input
              id=&quot;clientSecret&quot;
              type=&quot;password&quot;
              className=&quot;w-full p-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-400 focus:border-gray-400 bg-white&quot;
              value={clientSecret}
              onChange={(e) =&amp;gt; setClientSecret(e.target.value)}
            /&amp;gt;
          &amp;lt;/div&amp;gt;

          &amp;lt;div&amp;gt;
            &amp;lt;label
              className=&quot;block text-sm font-medium mb-1 text-gray-700&quot;
              htmlFor=&quot;authEndpoint&quot;
            &amp;gt;
              Authorization Endpoint
            &amp;lt;/label&amp;gt;
            &amp;lt;input
              id=&quot;authEndpoint&quot;
              type=&quot;text&quot;
              className=&quot;w-full p-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-400 focus:border-gray-400 bg-white&quot;
              value={authEndpoint}
              onChange={(e) =&amp;gt; setAuthEndpoint(e.target.value)}
            /&amp;gt;
          &amp;lt;/div&amp;gt;

          &amp;lt;div&amp;gt;
            &amp;lt;label
              className=&quot;block text-sm font-medium mb-1 text-gray-700&quot;
              htmlFor=&quot;tokenEndpoint&quot;
            &amp;gt;
              Token Endpoint
            &amp;lt;/label&amp;gt;
            &amp;lt;input
              id=&quot;tokenEndpoint&quot;
              type=&quot;text&quot;
              className=&quot;w-full p-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-400 focus:border-gray-400 bg-white&quot;
              value={tokenEndpoint}
              onChange={(e) =&amp;gt; setTokenEndpoint(e.target.value)}
            /&amp;gt;
          &amp;lt;/div&amp;gt;

          &amp;lt;div&amp;gt;
            &amp;lt;label
              className=&quot;block text-sm font-medium mb-1 text-gray-700&quot;
              htmlFor=&quot;redirectUri&quot;
            &amp;gt;
              Redirect URI
            &amp;lt;/label&amp;gt;
            &amp;lt;input
              id=&quot;redirectUri&quot;
              type=&quot;text&quot;
              className=&quot;w-full p-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-400 focus:border-gray-400 bg-white&quot;
              value={redirectUri}
              onChange={(e) =&amp;gt; setRedirectUri(e.target.value)}
            /&amp;gt;
          &amp;lt;/div&amp;gt;

          &amp;lt;div&amp;gt;
            &amp;lt;label
              className=&quot;block text-sm font-medium mb-1 text-gray-700&quot;
              htmlFor=&quot;scope&quot;
            &amp;gt;
              Scope
            &amp;lt;/label&amp;gt;
            &amp;lt;input
              id=&quot;scope&quot;
              type=&quot;text&quot;
              className=&quot;w-full p-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-400 focus:border-gray-400 bg-white&quot;
              value={scope}
              onChange={(e) =&amp;gt; setScope(e.target.value)}
            /&amp;gt;
          &amp;lt;/div&amp;gt;

          {error &amp;amp;&amp;amp; (
            &amp;lt;div className=&quot;bg-red-50 text-red-700 p-3 rounded border border-red-200&quot;&amp;gt;
              {error}
            &amp;lt;/div&amp;gt;
          )}

          {accessToken &amp;amp;&amp;amp; (
            &amp;lt;div className=&quot;bg-green-50 text-green-700 p-3 rounded border border-green-200&quot;&amp;gt;
              &amp;lt;p&amp;gt;Access Token: {accessToken.slice(0, 20)}...&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          )}

          {decodedToken &amp;amp;&amp;amp; (
            &amp;lt;div className=&quot;bg-gray-50 text-gray-700 p-3 rounded border border-gray-200&quot;&amp;gt;
              &amp;lt;h3 className=&quot;font-bold mb-2&quot;&amp;gt;Decoded ID Token:&amp;lt;/h3&amp;gt;
              &amp;lt;pre className=&quot;whitespace-pre-wrap overflow-x-auto&quot;&amp;gt;
                {JSON.stringify(decodedToken, null, 2)}
              &amp;lt;/pre&amp;gt;
            &amp;lt;/div&amp;gt;
          )}

          &amp;lt;button
            onClick={startOAuth}
            className=&quot;w-full bg-gray-700 text-white py-2 px-4 rounded hover:bg-gray-800 focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors&quot;
          &amp;gt;
            Start Google OAuth Flow
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default OAuthApp
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How can you detect these attacks&lt;/h2&gt;
&lt;p&gt;Generally, IdPs provide admins with a list of OAuth 2.0 apps authorized by each user. For instance, Google shows this list in the admin panel:
&lt;img src=&quot;/blog/chrome-extension-breach/admin-panel.png&quot; alt=&quot;Admin panel view&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;However the list doesn&apos;t show the full set of permissions granted to the app nor any risk they carry.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For example, in this case, it&apos;s not obvious that the app could upload a malicious Chrome extension unless you click on the application and read through the full description.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/chrome-extension-breach/admin-panel2.png&quot; alt=&quot;Admin panel view detail&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The security team should periodically review the list and the details of each grant to verify if any malicious app is present.&lt;/p&gt;
&lt;h2&gt;How SlashID can help&lt;/h2&gt;
&lt;p&gt;SlashID can assist in three ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Our Identity Graph shows the users who are using a given OAuth 2.0 application&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/blog/chrome-extension-breach/view.png&quot; alt=&quot;Admin panel view detail&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We provide built-in detections across IdPs for risky scopes and anomalous OAuth 2.0 apps to immediately detect these types of attacks - as shown in the screenshot below.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/blog/chrome-extension-breach/slashid-detection.png&quot; alt=&quot;Admin panel view detail&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You can automatically remediate an attack by revoking access to the app either automatically through our APIs or manually through the remediation playbook&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Now that traditional phishing attempts have been made harder by more awareness and stronger MFA options, attackers are naturally migrating towards new ways to compromise targets and OAuth 2.0 is a primary target for that due to its complexity and ubiquitous usage. &lt;a href=&quot;https://www.slashid.dev/contact/&quot;&gt;Contact us&lt;/a&gt; if you&apos;d like to see how SlashID can protect against this and other types of identity attacks!&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Introducing Organization Attributes</title><link>https://slashid.dev/blog/org-attributes/</link><guid isPermaLink="true">https://slashid.dev/blog/org-attributes/</guid><description>With organization attributes, you can now easily store and manage tenant-level data directly on our platform. </description><pubDate>Tue, 14 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introducing Organization Attributes: Store Tenant-Level Data&lt;/h2&gt;
&lt;p&gt;Today we are excited to announce a powerful new feature: Organization Attributes. With organization attributes, you can now easily store and manage tenant-level data directly on our platform.&lt;/p&gt;
&lt;p&gt;Key features include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Store data at the organization level, separate from user data&lt;/li&gt;
&lt;li&gt;Organize data into attribute &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/attribute_buckets&quot;&gt;&quot;buckets&quot;&lt;/a&gt; with different visibility and access levels&lt;/li&gt;
&lt;li&gt;Org-level, granular &lt;a href=&quot;https://www.slashid.dev/blog/app-layer-encryption/&quot;&gt;encryption&lt;/a&gt; with an hardware root of trust&lt;/li&gt;
&lt;li&gt;Multi-region replication for even higher availability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Typical use cases include storing billing data, tags, secrets, feature flags, and more.&lt;/p&gt;
&lt;h2&gt;How It Works&lt;/h2&gt;
&lt;p&gt;With the organization attributes APIs, you POST key-value pairs into named attribute buckets for a given organization ID. For example, to store some billing information and feature tags for ACME Corp, you could make a request like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X PUT https://api.slashid.com/organizations/attributes \
  -H &apos;SlashID-OrgID: 65e43220-439d-7ae5-9934-d1c4999c8d64&apos; \
  -H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
  -H &quot;Content-Type: application/json&quot; \
  -d &apos;{
    &quot;end_user_no_access&quot;: {
        &quot;billing&quot;: {
        &quot;plan&quot;: &quot;enterprise&quot;,
        &quot;status&quot;: &quot;paid&quot;,
        &quot;subscriptionEnd&quot;: &quot;2023-12-31&quot;
        },
        &quot;tags&quot;: {
        &quot;region&quot;: &quot;us-west&quot;,
        &quot;segment&quot;: &quot;enterprise&quot;
        }
    }
  }&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This stores the given attributes in the &lt;code&gt;end_user_no_access&lt;/code&gt; bucket (more on buckets below) for the given &lt;code&gt;SlashID-OrgID&lt;/code&gt;. To fetch all attributes across buckets:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X GET https://api.slashid.com/organizations/attributes \
  -H &apos;SlashID-OrgID: 65e43220-439d-7ae5-9934-d1c4999c8d64&apos; \
  -H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To fetch just the &lt;code&gt;billing&lt;/code&gt; attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X GET https://api.slashid.com/organizations/attributes/end_user_no_access?attributes=billing \
  -H &apos;SlashID-OrgID: 65e43220-439d-7ae5-9934-d1c4999c8d64&apos; \
  -H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And to delete the &lt;code&gt;tags&lt;/code&gt; attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X DELETE &quot;https://api.slashid.com/organizations/attributes/end_user_no_access?attributes=tags&quot; \
  -H &apos;SlashID-OrgID: 65e43220-439d-7ae5-9934-d1c4999c8d64&apos; \
  -H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simple and powerful! You can store up to 64KB per attribute value, with attribute names up to 70 bytes. Access is controlled via RBAC policies.&lt;/p&gt;
&lt;h2&gt;Access control and visibility&lt;/h2&gt;
&lt;p&gt;Earlier we briefly mentioned the concept of a bucket. A bucket in SlashID is a container of attributes with a specific level of access and visibility.&lt;/p&gt;
&lt;p&gt;You can see all available buckets for organization attributes with the following call:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X GET https://api.slashid.com/organizations/attribute-buckets \
  -H &apos;SlashID-OrgID: 65e43220-439d-7ae5-9934-d1c4999c8d64&apos; \
  -H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two core concepts apply to buckets, one is the &lt;strong&gt;sharing scope&lt;/strong&gt; and the other is the &lt;strong&gt;end-user permissions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Buckets can either be shared with the current organization exclusively or across organizations that share the same user pool. Here&apos;s an example output
of two buckets that are respectively shared only in the org and in the person pool:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    {
      &quot;end_user_permissions&quot;: &quot;no_access&quot;,
      &quot;name&quot;: &quot;end_user_no_access&quot;,
      &quot;owner_organization_id&quot;: &quot;65e43220-439d-7ae5-9934-d1c4999c8c54&quot;,
      &quot;sharing_scope&quot;: &quot;organization&quot;
    },

    {
      &quot;end_user_permissions&quot;: &quot;no_access&quot;,
      &quot;name&quot;: &quot;person_pool-end_user_no_access&quot;,
      &quot;sharing_scope&quot;: &quot;person_pool&quot;
    },
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Writing an attribute in the latter pool allows any organization with the same pool of users to access, delete, and modify that attribute.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The end user permissions allow to specify whether a given bucket should be accessible and writable by an authenticated user or whether they can only be accessed
through the backend with an API key.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This powerful primitive allows to have organization-level attributes that are easily visible and usable from the front end without any backend work required.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Common Use Cases&lt;/h2&gt;
&lt;p&gt;With organization attributes, you can now easily persist tenant configuration and metadata, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Billing data like plan, status, MRR, etc. Trigger flows based on changes.&lt;/li&gt;
&lt;li&gt;Tags for segmentation, targeting, categorization&lt;/li&gt;
&lt;li&gt;Feature flags to control rollouts and access&lt;/li&gt;
&lt;li&gt;Secrets&lt;/li&gt;
&lt;li&gt;Preferences and settings&lt;/li&gt;
&lt;li&gt;Arbitrary metadata to power your business logic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No longer do you have to set up separate databases or caches to store this type of organization-level data. It&apos;s now available with a simple API.&lt;/p&gt;
&lt;h2&gt;Get Started&lt;/h2&gt;
&lt;p&gt;Check out the &lt;a href=&quot;#https://developer.slashid.dev/docs/category/api/organization-attributes&quot;&gt;organization attributes API reference&lt;/a&gt; to learn more and start using this feature in your application today. We can&apos;t wait to see what you build with it!&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Single Sign-On implementation: Security Issues and Best Practices</title><link>https://slashid.dev/blog/oauth-security/</link><guid isPermaLink="true">https://slashid.dev/blog/oauth-security/</guid><description>Social logins and OpenID Connect (OIDC) are an extremely effective way to register new users with low friction.  There are many libraries out there that implement OIDC with several providers, however the implementation is very error-prone and can expose an application to account takeover attacks.  In this article, we’ll discuss the common security issues found in OAuth 2.0/OIDC login flows and best practices on how to avoid them.</description><pubDate>Mon, 08 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Social logins and enterprise connections are popular because they are an extremely effective way to register users lowering sign up frictions (often up to 20% higher conversion or more) as well as a way to connect internal enterprise directories to a SaaS application.&lt;/p&gt;
&lt;p&gt;Besides SAML, which is largely used for enterprise directories, the standard for social login is a protocol called OpenID Connect (OIDC). This is an identity layer built on top of the OAuth 2.0 framework. It allows third-party applications to verify the identity of the end-user and to obtain basic user profile information. OIDC uses JSON web tokens (JWTs), which are obtained through flows defined in the OAuth 2.0 specifications.&lt;/p&gt;
&lt;p&gt;Unfortunately OAuth 2.0 is a complex protocol and several implementations suffer from security issues.&lt;/p&gt;
&lt;p&gt;Just recently Salt Security published a series of blog &lt;a href=&quot;https://salt.security/blog/oh-auth-abusing-oauth-to-take-over-millions-of-accounts?&quot;&gt;posts&lt;/a&gt; on OAuth 2.0 implementation bugs found in sites such as Booking.com, Kayak, Grammarly, and many more.&lt;/p&gt;
&lt;p&gt;This is in addition to the vulnerability reported to Descope that can lead to &lt;a href=&quot;https://msrc.microsoft.com/blog/2023/06/potential-risk-of-privilege-escalation-in-azure-ad-applications/&quot;&gt;privilege escalation&lt;/a&gt; when using Entra/Azure AD for SSO and the one reported by Truffle Security that could lead to a similar &lt;a href=&quot;https://trufflesecurity.com/blog/google-oauth-is-broken-sort-of&quot;&gt;outcome&lt;/a&gt; with Google.&lt;/p&gt;
&lt;p&gt;In this blog post we summarize the best practices in implementing OAuth 2.0 securely followed by a deeper dive on the risk posed by each.&lt;/p&gt;
&lt;h2&gt;OAuth 2.0 and OIDC authorization at a high level&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Feel free to &lt;a href=&quot;#security-best-practices&quot;&gt;skip&lt;/a&gt; this section if you are already familiar with OAuth 2.0 and OIDC&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It would take too long to describe all the possible OAuth 2.0 flows and they are generally well documented online. To summarize in a picture:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/oauth2-security/flow.png&quot; alt=&quot;OAuth 2.0 flow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At a high-level, here is some contextual information useful for the rest of the article:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Roles&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Resource Owner&lt;/strong&gt;: The user who authorizes an application to access their account.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource Server&lt;/strong&gt;: The server hosting the user data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client&lt;/strong&gt;: The application wanting to access the user&apos;s account.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorization Server&lt;/strong&gt;: The server that authenticates the resource owner and issues access tokens to the client.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flow&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;client&lt;/strong&gt; requests authorization from the &lt;strong&gt;resource owner&lt;/strong&gt; to access their resources.&lt;/li&gt;
&lt;li&gt;If the &lt;strong&gt;resource owner&lt;/strong&gt; grants authorization, the &lt;strong&gt;client&lt;/strong&gt; receives an authorization grant, which is a credential representing the resource owner&apos;s authorization.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;client&lt;/strong&gt; requests an access token from the &lt;strong&gt;authorization server&lt;/strong&gt; by presenting the authorization grant and authentication.&lt;/li&gt;
&lt;li&gt;If the request is valid, the &lt;strong&gt;authorization server&lt;/strong&gt; issues an access token to the &lt;strong&gt;client&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;client&lt;/strong&gt; uses the access token to access the protected resources hosted by the &lt;strong&gt;resource server&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Authorization Grant Types&lt;/strong&gt;: OAuth 2.0 defines several grant types but in this article we&apos;ll touch on the two most commonly used ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authorization Code&lt;/strong&gt;: Used with web applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implicit&lt;/strong&gt;: Simplified flow, mostly used by mobile or web applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access Token&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This token represents the authorization of a specific application to access specific parts of a user&apos;s data.&lt;/li&gt;
&lt;li&gt;The client must send this token to the resource server in every request.&lt;/li&gt;
&lt;li&gt;Tokens are limited in scope and duration.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As it is often explained publicly, OAuth 2.0 doesn&apos;t provide an authentication flow hence it is generally used in conjuction to OpenID Connect (OIDC). OIDC allows clients to verify the identity of an end-user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end-user.
At a high-level:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Roles&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;End-User&lt;/strong&gt;: The individual whose identity is being verified.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client&lt;/strong&gt;: The application requiring the end-user&apos;s identity, typically a web app, mobile app, or server-side application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorization Server (OpenID Provider)&lt;/strong&gt;: The server that authenticates the end-user and provides identity tokens to the client.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flow&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;client&lt;/strong&gt; requests authorization from the &lt;strong&gt;end-user&lt;/strong&gt;. This is typically done through a user agent (like a web browser) and involves redirecting the user to the authorization server.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;authorization server&lt;/strong&gt; authenticates the &lt;strong&gt;end-user&lt;/strong&gt;. This may involve the user logging in with a username and password or other authentication methods.&lt;/li&gt;
&lt;li&gt;Once authenticated, the &lt;strong&gt;end-user&lt;/strong&gt; is asked to grant permission for the client to access their identity information.&lt;/li&gt;
&lt;li&gt;After the end-user grants permission, the &lt;strong&gt;authorization server&lt;/strong&gt; redirects back to the &lt;strong&gt;client&lt;/strong&gt; with an ID Token and, often, an Access Token.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;ID Token&lt;/strong&gt; is a JSON Web Token (JWT) that contains claims about the identity of the user. The client will validate this token to ensure it&apos;s authentic.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Access Token&lt;/strong&gt; can be used by the client to access the user&apos;s profile information via a user-info endpoint on the authorization server.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scopes and Claims&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;During the authorization request, the client specifies the scope of the access it is requesting. Common scopes in OpenID Connect include &lt;code&gt;openid&lt;/code&gt; (required), &lt;code&gt;profile&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Claims are pieces of information about the user, such as the user&apos;s name, email, and so forth. These are included in the ID Token based on the requested scopes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In practice, at a high level, a client is given a &lt;code&gt;client_id&lt;/code&gt; and a &lt;code&gt;client_secret&lt;/code&gt;. The client initiates a request sending those two parameters among others to the authorization server, the authorization server first verifies the validity of the &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; and then proceeds to verify the user. Once that&apos;s done, the user is redirected to a &lt;code&gt;redirect_uri&lt;/code&gt; passed to the authorization server by the client. From there on, depending on the OAuth 2.0 flow, there might be further exchanges between the client and the authorization server server.&lt;/p&gt;
&lt;h2&gt;Security best practices&lt;/h2&gt;
&lt;p&gt;This is the a list of security checks to perform when implementing OIDC:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Only use authoritative claims&lt;/li&gt;
&lt;li&gt;Enforce exact paths in OAuth 2.0 providers configuration&lt;/li&gt;
&lt;li&gt;Avoid the implicit OAuth 2.0 grant type&lt;/li&gt;
&lt;li&gt;Use PKCE to avoid CSRF and interception attacks&lt;/li&gt;
&lt;li&gt;Verify the token received from a third-party IdP and check that the client id is the intended client id for the application&lt;/li&gt;
&lt;li&gt;Be mindful of which providers you accept on your website&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Only use authoritative claims&lt;/h2&gt;
&lt;p&gt;This issue has presented itself in a number of different flavors across major identity providers including Microsoft Entra, Google and AWS Cognito. For example,
Flickr was compromised through a version of this &lt;a href=&quot;https://security.lauritz-holtmann.de/advisories/flickr-account-takeover/&quot;&gt;issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Microsoft calls the issue &quot;the false identifier anti-pattern&quot;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The false identifier &lt;a href=&quot;https://techcommunity.microsoft.com/t5/microsoft-entra-blog/the-false-identifier-anti-pattern/ba-p/3846013&quot;&gt;anti-pattern&lt;/a&gt; occurs when an application or service assumes that an attribute other than the subject of an assertion from a federated identity provider is a unique, durable, and trustworthy account identifier during single sign-on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Microsoft Entra&lt;/h3&gt;
&lt;p&gt;The issue stems from the fact that in AzureAD/Entra a user without a provisioned mailbox can have any email address set for their Mail (Primary SMTP) attribute. This attribute is not guaranteed to come from a verified email address.&lt;/p&gt;
&lt;p&gt;However most clients retrieving the email address claim through OIDC would treat it as a verified/authoritative email address.&lt;/p&gt;
&lt;p&gt;For example, an attacker could have an Azure AD account with an email such as &lt;code&gt;satya.nadella@microsoft.com&lt;/code&gt; and the receiving client would treat that as the valid
email address for that user. If Satya Nadella actually had an account in that specific client that could lead to a privilege escalation/unauthorized impersonation where the attacker could impersonate Nadella.&lt;/p&gt;
&lt;p&gt;It&apos;s important to note that while this specific issue was about the email address claim in AzureAD, the more general pattern is for clients to always verify which claims are authoritative for a Resource Server and maintain the same &quot;privileges&quot; over them.&lt;/p&gt;
&lt;h3&gt;Google&lt;/h3&gt;
&lt;p&gt;In fact, similarly to the Entra issues, researchers at Truffle Security discovered a very similar &lt;a href=&quot;https://trufflesecurity.com/blog/google-oauth-is-broken-sort-of/&quot;&gt;issue&lt;/a&gt; with Google.
In particular, it is possible to create a Google account with an existing email. Contrary to the AzureAD case, the ownership of the email is verified. However depending on the implementation of the client this could still lead to two issues:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Insider Threat: an existing employee can create a shadow account (by leveraging the + sign aliases) in the target client/application. If the application allows for multiple idenfiers to be specified or very long-lived tokens, the insider might be able to maintain access to the shadow account even after the employee has left the company/has been terminated. Such a vulnerability was found by Truffle Security in both &lt;a href=&quot;https://trufflesecurity.com/blog/google-oauth-is-broken-sort-of&quot;&gt;Zoom and Slack&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Privilege escalation/Unauthorized impersonation: Several researchers have shown how somebody can abuse customer support ticketing systems to impersonate other domains using &lt;a href=&quot;https://medium.com/intigriti/how-i-hacked-hundreds-of-companies-through-their-helpdesk-b7680ddc2d4c&quot;&gt;magic links&lt;/a&gt;. In such a scenario, an attacker could gain access to a company application by combining the two issues together.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: Google has a OAuth 2.0 claim called &lt;code&gt;email_verified&lt;/code&gt;, it might be possible to create Google accounts without any email verification at all leading to more unauthorized impersonation issues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;AWS Cognito&lt;/h3&gt;
&lt;p&gt;The last example is another variation on this but easier to follow. A website can use AWS Cognito as an OAuth 2.0 provider leading to a similar issue.&lt;/p&gt;
&lt;p&gt;As well described in this blog &lt;a href=&quot;https://security.lauritz-holtmann.de/advisories/flickr-account-takeover/&quot;&gt;post on Flickr&lt;/a&gt;, by default Cognito allows users to change their user attributes, including the email attribute &lt;code&gt;email&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the case of Flickr, the website was using the standard OIDC flow to authenticate a user and they referenced the &lt;code&gt;email&lt;/code&gt; claim without verying the flag in the &lt;code&gt;email_verified&lt;/code&gt; claim. This lead to a scenario where an attacker could takeover any Flickr account with a simple process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an attacker account on Flickr and authenticate&lt;/li&gt;
&lt;li&gt;Extract the AWS Cognito access token and using the aws cli change the &lt;code&gt;email&lt;/code&gt; claim to the victim email&lt;/li&gt;
&lt;li&gt;Re-login with the attacker account in (1)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Flickr use the &lt;code&gt;email&lt;/code&gt; claim to link the Flickr account to the AWS Cognito user resulting in an arbitrary account takeover.&lt;/p&gt;
&lt;h2&gt;Enforce exact paths in OAuth 2.0 providers configuration&lt;/h2&gt;
&lt;p&gt;When a client registers with an Authorization Server generally a &lt;code&gt;redirect_uri&lt;/code&gt; has to be specified, the Authorization Server
will then verify that the URL passed during the OIDC flow matches the &lt;code&gt;redirect_uri&lt;/code&gt; stored for that client.&lt;/p&gt;
&lt;p&gt;Unfortunately most Authorization Servers allow wildcards in the path or some even allow no &lt;code&gt;redirect_uri&lt;/code&gt; at all. There&apos;s nothing
the client can do in the latter case but in the former it is crucial to specify exact urls instead of wildcards.&lt;/p&gt;
&lt;p&gt;This is because an attacker can potentially trick a victim into being redirected to a different origin than intended and thus potentially stealing the access token.&lt;/p&gt;
&lt;h2&gt;Avoid the implicit OAuth 2.0 grant type&lt;/h2&gt;
&lt;p&gt;The implicit OAuth 2.0 grant type was previously recommended for native apps and SPA applications. In this flow, the access token is returned directly
as part of the redirect.&lt;/p&gt;
&lt;p&gt;Combining this with the previous issue, can lead to a simple way to impersonate the user by stealing the access token when the user is redirected to a
&lt;code&gt;redirect_uri&lt;/code&gt; that is attacker-controlled.&lt;/p&gt;
&lt;h2&gt;Use Proof Key for Code Exchange (PKCE) to avoid CSRF and interception attacks&lt;/h2&gt;
&lt;p&gt;Cross-Site Request Forgery (CSRF) is a vulnerability that occurs when an attacker can cause a victim to perform an unintended action on a web resource.
In the context of OAuth 2.0, a code interception attack is an attack where the attacker intercepts an authorization code and tries to redeem it for a valid access token.&lt;/p&gt;
&lt;p&gt;A technique called Proof Key for Code Exchange (PKCE, pronounced &quot;pixy&quot;) was introduced to reduce the risk of CSRF and code interception. PKCE is an addition to any OAuth 2.0 flows.&lt;/p&gt;
&lt;p&gt;In summary, the idea is for the application/client to select a secret, one-time nonce that cannot be intercepted by an attacker as it is never sent to the user browser.&lt;/p&gt;
&lt;p&gt;When a flow starts, the client hashes the nonce and sends it in the &lt;code&gt;code_challenge&lt;/code&gt; parameter to the authorization server.
When the user completes the authorization flow and is redirected to the &lt;code&gt;redirect_uri&lt;/code&gt;, the client sends the &lt;code&gt;code_verifier&lt;/code&gt; in addition to the auth code and the state parameters to the authorization server. The authorization server returns a valid access token only if the hash of the &lt;code&gt;code_verified&lt;/code&gt; matches the &lt;code&gt;code_challenge&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The key idea is that an attacker who intercepts the authorization code from the server is unable to redeem it for an access token, as they are not in possession of the &lt;code&gt;code_verifier&lt;/code&gt; secret.&lt;/p&gt;
&lt;p&gt;The authorization server would reject a token request if the attacker tries to inject an authorization code via CSRF.&lt;/p&gt;
&lt;p&gt;PKCE also protects against a network attacker because the attacker would need to inject a code that is bound to the same code challenge that was used initially by the client. While the attacker can create codes bound to arbitrary code challenges, the attacker cannot know the code challenge used in any one legitimate session.&lt;/p&gt;
&lt;h2&gt;Verify the token received from a third-party IdP&lt;/h2&gt;
&lt;p&gt;If you do need to support the implicit OAuth 2.0 grant type it is key to verify the token received in the callback as the callback could be invoked by an attacker with a valid
access token belonging to a different website.&lt;/p&gt;
&lt;p&gt;In this scenario, an attacker can intercept the OAuth 2.0 flow and swap the token with a token issued from an attacker-controlled website. Specifically:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Trick a user into logging-in through OIDC on an attacker-controlled website&lt;/li&gt;
&lt;li&gt;Begin the OIDC flow on the target website pretending to be the user. This way the attacker is able to capture the &lt;code&gt;state&lt;/code&gt; parameter used by the client and the Authorization Server to identify a &quot;session&quot;&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;state&lt;/code&gt; parameter from (2) combined with the access token for the user obtained in (1) to complete the authentication flow on the target client/website.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To address this problem, it is crucial to confirm that the token received was issued for the client &lt;code&gt;client_id&lt;/code&gt; and not for a third party application.&lt;/p&gt;
&lt;h2&gt;Be mindful of which providers you accept on your website&lt;/h2&gt;
&lt;p&gt;Duplicated accounts are one of the most vexing issues for both users and companies. On the user side, duplicate accounts lead to frustration and bad experience.
On the company side it leads to customer support tickets as well as issues with data analysis.&lt;/p&gt;
&lt;p&gt;To address this problem a lot of websites automatically merge accounts if the primary user identifier is the same. In other words, a user who registered with &lt;code&gt;user@example.com&lt;/code&gt; and has account number &lt;code&gt;123&lt;/code&gt; will be treated as the same person if the primary identifier &lt;code&gt;user@example.com&lt;/code&gt; comes from OIDC, a magic link or any other authentication method.&lt;/p&gt;
&lt;p&gt;This is a more general case of the issues we&apos;ve see in (1), specifically an attacker could impersonate a legitimate user if account merging is turned on a website and the website has one of two issues:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Has a weakness such as trusting a claim that is not &lt;a href=&quot;#verify-which-claims-are-considered-authoritative-by-the-source-directory&quot;&gt;authoritative&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Supports a OIDC provider for which an attacker can obtain a token and the website is not implementing countermeasures as indicated in the section &lt;a href=&quot;#verify-the-token-received-from-a-third-party-idp&quot;&gt;above&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;If you are a SlashID customer&lt;/h2&gt;
&lt;p&gt;Ultimately mitigating these issues requires the expert review of a security engineer or an application security review. However, using a vendor can make the task less daunting.
In particular, if you are a SlashID customer we help you in the following ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We do not support OAuth 2.0 implicit flow&lt;/li&gt;
&lt;li&gt;We enforce PKCE&lt;/li&gt;
&lt;li&gt;For all Identity Providers we support, we return an email claim only if it&apos;s verified&lt;/li&gt;
&lt;li&gt;We only support SSO providers that we have vetted and consider trustworthy&lt;/li&gt;
&lt;li&gt;You can use our SDK to send an email verification link after the user logs in with SSO&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Authentication is a typical example of a task that is simple in theory but very hard in practice due to both security and reliability issues. In this blog post we&apos;ve shown a few examples of what could go wrong when implementing OIDC/Social Logins.&lt;/p&gt;
&lt;p&gt;If you are interested in implementing social logins and identity securely, get a free account &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=oauth_security&quot;&gt;here&lt;/a&gt;
or reach out to &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;us&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>OAuth 2.0 Fine-Grained API Authorization with Gate and OpenAPI</title><link>https://slashid.dev/blog/openapi_oauth2_gate/</link><guid isPermaLink="true">https://slashid.dev/blog/openapi_oauth2_gate/</guid><description>Protect your API against unauthorized access without changing your application. Our newest Gate plugin automatically enforces OpenAPI security checks, so you can implement fine-grained access control for your APIs and workloads without writing any extra code.</description><pubDate>Mon, 23 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins/enforce-openapi-security&quot;&gt;latest plugin for SlashID Gate&lt;/a&gt;
enforces all the security schemes defined in an OpenAPI spec.&lt;/p&gt;
&lt;p&gt;You can keep your OpenAPI spec as the single source of truth for describing your API, and deploy Gate at the edge
to make sure all of your security schemes are fully enforced before a request reaches your service.
Did your API change? No problem - just roll out a new Gate deploy with the updated specification.&lt;/p&gt;
&lt;p&gt;In this blogpost we&apos;ll explain the advantages of using OAuth 2.0 to secure your APIs, how you can describe this in an
OpenAPI spec, and how to use Gate to enforce it - all without writing a single line of code.&lt;/p&gt;
&lt;p&gt;This plugin works with any OAuth 2.0 provider, but for this example we&apos;ll use SlashID, as it only takes a few minutes to
get up and running with OAuth 2.0 client credentials.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;Long-lived and overly privileged API keys are one of the primary sources of data breaches today.
As a result, enterprise companies&apos; RFPs are increasingly requiring vendors to protect their APIs using two-legged or three-legged OAuth 2.0 flows with fine-grained access control.&lt;/p&gt;
&lt;p&gt;In this blogpost, we&apos;ll demonstrate how to quickly add and enforce client credentials for your APIs to comply with
two-legged OAuth 2.0 flow requirements, including out-of-the-box fine-grained access control.&lt;/p&gt;
&lt;p&gt;While there are many choices of Authorization Server, including SlashID, that can provision client credentials and access tokens,
SlashID is the only solution that also enforces access token validation for protected resources.&lt;/p&gt;
&lt;p&gt;In this section we provide an overview of OAuth 2.0 Client Credentials and the OpenAPI specification.
If you are already familiar with these topics, feel free to skip them.&lt;/p&gt;
&lt;h3&gt;OAuth 2.0 Client Credentials Flow&lt;/h3&gt;
&lt;p&gt;The OAuth 2.0 specification defines multiple authentication flows. One of the simplest is the &lt;a href=&quot;https://oauth.net/2/grant-types/client-credentials/&quot;&gt;&lt;strong&gt;client credentials&lt;/strong&gt; flow&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Request an access token from the Authorization Server using client credentials (for example, an ID and secret)&lt;/li&gt;
&lt;li&gt;Make a request to the Resource Server that includes the access token&lt;/li&gt;
&lt;li&gt;Resource Server validates the token&lt;/li&gt;
&lt;li&gt;Resource Server responds with requested data or an error depending on the outcome.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This flow is well-suited to machine-to-machine (M2M) authentication as there is no user interaction required (unlike
other OAuth 2.0 flows). The client credentials flow offers two significant advantages over API keys, which are often
used for M2M authentication scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Access tokens are typically short-lived&lt;/li&gt;
&lt;li&gt;Client credentials and access tokens are scoped by specification, meaning they have limited permissions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both of these features reduce the risk and limit the potential damage from credential theft, and help to enforce the
principle of least privilege, which is essential for both security and compliance.&lt;/p&gt;
&lt;p&gt;Implementing a system that uses this flow to protect endpoints comes with challenges. While there are many choices of
Authorization Server (including SlashID) that can provision client credentials and access tokens, it is up to the
maintainer of the protected resource to correctly check access tokens. This means every endpoint needs
to validate an access token and its scope before deciding whether the request is allowed, and this logic needs to be kept
up to date and consistent with changing API requirements. A single missed scope can leave a sensitive API exposed to
clients that should not have access.&lt;/p&gt;
&lt;h3&gt;OpenAPI&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.openapis.org/&quot;&gt;OpenAPI specification&lt;/a&gt; is an interface definition language for describing web services.
OpenAPI 3.0 and its predecessor Swagger are two of the &lt;a href=&quot;https://www.postman.com/state-of-api/api-technologies/#api-technologies&quot;&gt;most popular technologies&lt;/a&gt;
when working with APIs. An OpenAPI specification can completely describe your API, and is useful both as a source of truth
while developing and for generating code.&lt;/p&gt;
&lt;p&gt;In particular, OpenAPI supports security schemes, which are used to describe how a given endpoint should be authorized.
OpenAPI supports HTTP, API key, OAuth 2.0, and OIDC security, each with their own set of fields. OAuth 2.0 security schemes
include a set of scopes that are required for a given operation (method on an endpoint) to be allowed.&lt;/p&gt;
&lt;p&gt;However, this too presents implementation challenges - you need to make sure that your API server correctly enforces the latest
security schemes for each endpoint. Generated code can help with this, but again, a small mistake can leave sensitive
APIs exposed.&lt;/p&gt;
&lt;h2&gt;Gate to the Rescue&lt;/h2&gt;
&lt;p&gt;How do you take advantage of the security of client credentials and the convenience of an OpenAPI specification while
making sure that security schemes are always correctly enforced? Enter Gate, SlashID&apos;s identity-aware authorization
service. Gate is flexible and simple-to-use, and can be deployed as a standalone proxy or as a sidecar to
your existing API gateway. Gate is part of a growing movement towards authentication/authorization at the edge, which
is being embraced by &lt;a href=&quot;https://www.slashid.dev/products/gate/&quot;&gt;organizations that are serious about security&lt;/a&gt;.
For more information on Gate, check our &lt;a href=&quot;https://developer.slashid.dev/docs/gate&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By enforcing the security schemes in an OpenAPI document, our &lt;a href=&quot;https://developer.slashid.dev/docs/gate/plugins/enforce-openapi-security&quot;&gt;new OpenAPI security plugin&lt;/a&gt;
solves both of the problems described above (and more). Let&apos;s see this in action.&lt;/p&gt;
&lt;h2&gt;Gate in Action&lt;/h2&gt;
&lt;p&gt;Let&apos;s see Gate&apos;s OpenAPI plugin in action. We&apos;re going to do four main steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write an OpenAPI document describing our API, using the OAuth 2.0 client credentials flow security scheme&lt;/li&gt;
&lt;li&gt;Create scoped client credentials and access tokens with SlashID&lt;/li&gt;
&lt;li&gt;Deploy Gate in front of a backend, configured to enforce the security from the OpenAPI document&lt;/li&gt;
&lt;li&gt;Make requests via Gate to the backend with different access tokens and see security enforcement in action.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In particular, the example backend service will not include any logic for validating access tokens - Gate takes care of it,
so you can focus on building the features your organization needs.&lt;/p&gt;
&lt;h3&gt;OpenAPI Document&lt;/h3&gt;
&lt;p&gt;Below we have a short OpenAPI document describing a simple customer management API, which we imagine is exposed by one
of your services, intended for use by your other services and trusted partners. You want to make sure that least privilege
is honoured, and that access to each endpoint is controlled by fine-grained permissions.&lt;/p&gt;
&lt;p&gt;As such, you have defined an OAuth 2.0 security scheme using the client credentials flow and four scopes: one each for
the usual CRUD operations on customers. For each operation, you have specified that the OAuth 2.0 security scheme should
be used, and which scopes apply (so a &lt;code&gt;POST /customers&lt;/code&gt; request would require permissions to read and create customers).&lt;/p&gt;
&lt;p&gt;Note that the document also has a top-level &lt;code&gt;security&lt;/code&gt; field. This would be applied to any operation that did not have
its own security defined (although this is not the case for any operations in this document).&lt;/p&gt;
&lt;p&gt;This is a good start - you have defined a straightforward API and described its security model. Now you need to enforce
it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openapi: 3.0.1

info:
  title: Test API
  version: &apos;1.0&apos;

servers:
  - url: &apos;https://example.local&apos;

components:
  securitySchemes:
    OAuth2ClientCreds:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: &apos;https://api.slashid.com/oauth2/tokens&apos;
          scopes:
            customers:read: Read information about customers
            customers:create: Create a customer
            customers:modify: Modify existing customers
            customers:delete: Delete existing customers

paths:
  /customers:
    post:
      security:
        - OAuth2ClientCreds: [customers:read, customers:create]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - customer_name
                - customer_tax_id
              properties:
                customer_name:
                  type: string
                customer_tax_id:
                  type: string
        required: true
      responses:
        &apos;201&apos;:
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  customer_id:
                    type: string

  /customers/{customer_id}:
    parameters:
      - name: customer_id
        in: path
        required: true
        schema:
          type: string

    get:
      security:
        - OAuth2ClientCreds: [customers:read]
      responses:
        &apos;200&apos;:
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  customer_id:
                    type: string
                  customer_name:
                    type: string
                  customer_tax_id:
                    type: string

    patch:
      security:
        - OAuth2ClientCreds: [customers:read, customers:modify]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                customer_name:
                  type: string
                customer_tax_id:
                  type: string
        required: true
      responses:
        &apos;200&apos;:
          description: OK

    delete:
      security:
        - OAuth2ClientCreds: [customers:read, customers:delete]
      responses:
        &apos;200&apos;:
          description: OK

security:
  - OAuth2ClientCreds:
      [customers:read, customers:create, customers:modify, customers:delete]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Client Credentials&lt;/h3&gt;
&lt;p&gt;The next step is to create some client credentials, as Gate will need these in its configuration. We will use SlashID
as the Authorization Server, but you can use any Oauth 2.0 provider that supports the client credentials flow.
You can &lt;a href=&quot;https://console.slashid.dev/signup&quot;&gt;get started with SlashID&lt;/a&gt; in 30 seconds, and then
creating a set of client credentials is a &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-oauth-2-clients&quot;&gt;straightforward API call&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl --location &apos;https://api.slashid.com/oauth2/clients&apos; \
--header &apos;SlashID-OrgID: &amp;lt;ORGANIZATION ID&amp;gt;&apos; \
--header &apos;Content-Type: application/json&apos; \
--header &apos;SlashID-API-Key: &amp;lt;API KEY&amp;gt;&apos; \
--data &apos;{
    &quot;scopes&quot;: [&quot;customers:read&quot;, &quot;customers:create&quot;, &quot;customers:modify&quot;, &quot;customers:delete&quot;],
    &quot;client_name&quot;: &quot;example&quot;,
    &quot;grant_types&quot;: [&quot;client_credentials&quot;]
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;result&quot;: {
    &quot;client_id&quot;: &quot;&amp;lt;CLIENT ID&amp;gt;&quot;,
    &quot;client_name&quot;: &quot;example&quot;,
    &quot;client_secret&quot;: &quot;&amp;lt;CLIENT SECRET&amp;gt;&quot;,
    &quot;grant_types&quot;: [&quot;client_credentials&quot;],
    &quot;public&quot;: false,
    &quot;response_types&quot;: [],
    &quot;scopes&quot;: [
      &quot;customers:create&quot;,
      &quot;customers:delete&quot;,
      &quot;customers:modify&quot;,
      &quot;customers:read&quot;
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the client ID and secret as we will need them for the next steps.&lt;/p&gt;
&lt;h3&gt;Gate Deployment&lt;/h3&gt;
&lt;p&gt;We will deploy the Gate Docker image and a simple &lt;a href=&quot;https://hub.docker.com/r/kicbase/echo-server&quot;&gt;echo server backend&lt;/a&gt; using Docker Compose.
The backend service will respond to all incoming requests with a response describing the incoming request.
(Note that this means it does not implement the API described above, but is sufficient to demonstrate that the security schemes are being enforced.)&lt;/p&gt;
&lt;p&gt;First, let&apos;s configure Gate to enforce the security schemes defined in the OpenAPI specification.
Note that the &lt;code&gt;enforce-openapi-security&lt;/code&gt; plugin is enabled, meaning it is applied
to all URLs unless explicitly disabled. This means the plugin will be applied to all the endpoints defined in the
OpenAPI document.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gate:
  mode: proxy
  port: 5000
  tls:
    enabled: false
  log:
    format: text
    level: trace
  default:
    target: backend:8080

  plugins:
    - id: customers_openapi
      type: enforce-openapi-security
      enabled: true
      parameters:
        openapi_spec_url: &apos;/gate/openapi_customers.yaml&apos;
        openapi_spec_format: yaml
        oauth2_token_format: opaque
        oauth2_token_introspection_url: &apos;https://api.slashid.com/oauth2/tokens/introspect&apos;
        oauth2_token_introspection_client_id: &apos;&amp;lt;CLIENT ID&amp;gt;&apos;
        oauth2_token_introspection_client_secret: &apos;&amp;lt;CLIENT SECRET&amp;gt;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can write the Docker Compose file with the two services: Gate and the echo backend.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3.7&apos;

services:
  backend:
    image: kicbase/echo-server:1.0

  gate-proxy:
    image: slashid/gate-free:latest # or slashid/gate-enterprise:latest for enterprise customers
    volumes:
      - ./gate.yaml:/gate/gate.yaml
      - ./openapi_customers.yaml:/gate/openapi_customers.yaml
    ports:
      - &apos;5000:5000&apos;
    command: --yaml /gate/gate.yaml
    restart: on-failure
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the Gate container has two volumes defined:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gate.yaml&lt;/code&gt; is the Gate configuration file from above&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openapi_customers.yaml&lt;/code&gt; is the OpenAPI document from above.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run &lt;code&gt;docker-compose up&lt;/code&gt; to start Gate and the backend service.&lt;/p&gt;
&lt;h3&gt;Create Access Tokens and Make Requests&lt;/h3&gt;
&lt;p&gt;We&apos;ll begin by creating some access tokens using the client credentials from above. We will give each token a different
set of scopes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;customers:read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customers:create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customers:read&lt;/code&gt;, &lt;code&gt;customers:create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customers:read&lt;/code&gt;, &lt;code&gt;customers:modify&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customers:read&lt;/code&gt;, &lt;code&gt;customers:delete&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customers:read&lt;/code&gt;, &lt;code&gt;customers:create&lt;/code&gt;, &lt;code&gt;customers:modify&lt;/code&gt;, &lt;code&gt;customers:delete&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To obtain an access token, make an API call to the &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-oauth-2-tokens&quot;&gt;SlashID &lt;code&gt;/oauth2/tokens&lt;/code&gt; endpoint&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl --location &apos;https://api.slashid.com/oauth2/tokens&apos; \
--header &apos;Content-Type: application/x-www-form-urlencoded&apos; \
--header &apos;Authorization: Basic &amp;lt;Encoded CLIENT ID and CLIENT SECRET&amp;gt;&apos; \
--data-urlencode &apos;grant_type=client_credentials&apos; \
--data-urlencode &apos;scope=customers:read customers:create&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;access_token&quot;: &quot;&amp;lt;ACCESS TOKEN&amp;gt;&quot;,
  &quot;expires_in&quot;: 3599,
  &quot;scope&quot;: &quot;customers:read customers:create&quot;,
  &quot;token_type&quot;: &quot;bearer&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that this endpoint is authorized with HTTP Basic authorization using the client ID and client secret, and the scopes
are provided as a space-separated list (as per the &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7662&quot;&gt;OAuth 2.0 specification&lt;/a&gt;).
If you are using a different OAuth 2.0 provider you will need to modify the request accordingly.&lt;/p&gt;
&lt;p&gt;We can repeat this with different &lt;code&gt;scope&lt;/code&gt; value to obtain the six access tokens.&lt;/p&gt;
&lt;p&gt;Now we can make requests to the backend via Gate with different tokens.&lt;/p&gt;
&lt;p&gt;First, let&apos;s make a &lt;code&gt;GET&lt;/code&gt; request using the token with scope &lt;code&gt;customers:read&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl --location &apos;http://localhost:5000/customers/cid123&apos; \
--header &apos;Authorization: Bearer &amp;lt;ACCESS TOKEN WITH customers:read&amp;gt;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt; HTTP/1.1 200 OK

Request served by 96502fea2551

HTTP/1.1 GET /customers/cid123

Host: backend:8080
Accept: */*
Accept-Encoding: gzip, deflate, br
Authorization: Bearer &amp;lt;ACCESS TOKEN&amp;gt;
Cache-Control: no-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We got a &lt;code&gt;200&lt;/code&gt; status code and a response body from the echo server describing the request it received.&lt;/p&gt;
&lt;p&gt;However, if we try to &lt;code&gt;POST&lt;/code&gt; a customer with the same token:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;http://localhost:5000/customers&apos; \
--header &apos;Authorization: Bearer &amp;lt;ACCESS TOKEN WITH customers:read&amp;gt;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt; HTTP/1.1 403 Forbidden
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This time we receive a &lt;code&gt;403&lt;/code&gt; status code - the request is forbidden because the token does not have the correct scope
to carry out this operation (creating a new customer).&lt;/p&gt;
&lt;p&gt;On the other hand, if we create a token with scope &lt;code&gt;customers:read customers:create&lt;/code&gt;, and make a POST request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;http://localhost:5000/customers&apos; \
--header &apos;Authorization: Bearer &amp;lt;ACCESS TOKEN WITH customers:read customers:create&amp;gt;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt; HTTP/1.1 200 OK

Request served by 96502fea2551

HTTP/1.1 POST /customers

Host: backend:8080
Accept: */*
Accept-Encoding: gzip, deflate, br
Authorization: Bearer &amp;lt;ACCESS TOKEN&amp;gt;
Cache-Control: no-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This time we get a &lt;code&gt;200&lt;/code&gt; response and the echo - we successfully created a new customer, since our token had the correct scope.
(We see here the difference between the echo server - which always responds with a &lt;code&gt;200&lt;/code&gt; - and the OpenAPI document, which specifies a &lt;code&gt;201&lt;/code&gt; response on a successful &lt;code&gt;POST&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;The table below shows the response status code for each access token for different requests.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;table-wide&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token scope&lt;/th&gt;
&lt;th&gt;POST /customers&lt;/th&gt;
&lt;th&gt;GET /customers/cid123&lt;/th&gt;
&lt;th&gt;PATCH /customers/cid123&lt;/th&gt;
&lt;th&gt;DELETE /customers/cid123&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customers:read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;td&gt;200 OK&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customers:create&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customers:read&lt;/code&gt; &lt;code&gt;customers:create&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;200 OK&lt;/td&gt;
&lt;td&gt;200 OK&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customers:read&lt;/code&gt; &lt;code&gt;customers:modify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;td&gt;200 OK&lt;/td&gt;
&lt;td&gt;200 OK&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customers:read&lt;/code&gt; &lt;code&gt;customers:delete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;td&gt;200 OK&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;td&gt;200 OK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customers:read&lt;/code&gt; &lt;code&gt;customers:create&lt;/code&gt; &lt;code&gt;customers:modify&lt;/code&gt; &lt;code&gt;customers:delete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;201 Created&lt;/td&gt;
&lt;td&gt;200 OK&lt;/td&gt;
&lt;td&gt;200 OK&lt;/td&gt;
&lt;td&gt;200 OK&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;In addition, we can try some other requests:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for a path not defined in the spec: &lt;code&gt;404 Not Found&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;for a method not defined for a given path: &lt;code&gt;405 Method Not Allowed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;for a defined path and method, but without a token, or with an invalid token: &lt;code&gt;403 Forbidden&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Note that the first two behaviors can be modified with the &lt;code&gt;allow_requests_not_in_spec&lt;/code&gt; parameter, which will
allow requests that do not have a matching operation in the provided OpenAPI document. This should be used with
caution.)&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this blogpost we&apos;ve described how OAuth 2.0 client credentials and OpenAPI can help secure your services, and how
Gate can simplify this down to a few simple steps.&lt;/p&gt;
&lt;p&gt;In a future blogpost, we&apos;ll show you how to use similar approach to easily create custom rate limiting policies
for your APIs by using an OpenAPI document as the source of truth for Gate&apos;s configuration.&lt;/p&gt;
&lt;p&gt;Want to try out Gate? Check our &lt;a href=&quot;https://developer.slashid.dev/docs/gate&quot;&gt;documentation&lt;/a&gt;!
Ready to try SlashID? Sign up &lt;a href=&quot;https://console.slashid.dev/signup/gate?utm_source=openapigate-bp&quot;&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 Gate and have some feedback? &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;Let us know&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Joseph Gardner, Vincenzo Iozzo</author></item><item><title>Passkeys Adoption Trends: Survey from Large Deployments</title><link>https://slashid.dev/blog/passkeys-adoption/</link><guid isPermaLink="true">https://slashid.dev/blog/passkeys-adoption/</guid><description>In this comprehensive blog post, we delve into the publicly available data on large-scale passkeys rollouts, examining results, conversion rates, and implementation challenges as documented in engineering blogs by companies like Kayak and Yahoo Japan.</description><pubDate>Wed, 31 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Sign up for free &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=passkey-adoption&quot;&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=&quot;../passkeys-deepdive&quot;&gt;articles&lt;/a&gt;, we&apos;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&apos;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;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;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=&quot;https://engineering.mercari.com/en/blog/entry/20230810-mercaris-passkey-adoption/&quot;&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&apos;re ready to explore passkeys for your business, SlashID is here to assist. Don&apos;t hesitate to &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;reach out to us&lt;/a&gt; or sign up for free &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=passkey-adoption&quot;&gt;here&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Passkeys - Threat modeling and implementation considerations</title><link>https://slashid.dev/blog/passkeys-security-implementation/</link><guid isPermaLink="true">https://slashid.dev/blog/passkeys-security-implementation/</guid><description>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.</description><pubDate>Wed, 24 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Read up on how to add Passkeys to any application without code &lt;a href=&quot;https://www.slashid.dev/blog/gate-passkeys&quot;&gt;modifications&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&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.
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 an example implementation: &lt;a href=&quot;https://github.com/slashid/org-switch-demo-public&quot;&gt;GitHub&lt;/a&gt; and &lt;a href=&quot;https://multitenancy-demo.slashid.dev/&quot;&gt;live&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;

&lt;video style=&quot;width:100%&quot; autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/blog/all-passkeys/login.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&gt;
&lt;/p&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;&lt;a href=&quot;#passkeys-101&quot;&gt;Passkeys 101&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#security-and-threat-modeling&quot;&gt;Security and threat model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#implementation-considerations&quot;&gt;Implementation considerations&lt;/a&gt;&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;&lt;a href=&quot;#what-are-passkeys&quot;&gt;What are passkeys?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#why-now&quot;&gt;Why now?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#how-are-credentials-shared-and-stored&quot;&gt;How are credentials shared and stored?&lt;/a&gt;&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.
Websites can create or verify credentials through a web API called WebAuthn, implemented in all major browsers. 
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&apos;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.  For example, Apple &lt;a href=&quot;https://developer.apple.com/news/?id=mgdnfp8w&quot;&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.
According to the WebAuthn standards, there are two types of credentials:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/webauthn/#discoverable-credential&quot;&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=&quot;https://www.w3.org/TR/webauthn/#server-side-credential&quot;&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=&quot;https://en.wikipedia.org/wiki/Key_wrap&quot;&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;/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=&quot;https://www.w3.org/TR/webauthn/#sctn-credential-loss-key-mobility&quot;&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.
In a previous &lt;a href=&quot;https://www.slashid.dev/blog/passkeys-deepdive/&quot;&gt;blog post&lt;/a&gt;, we analyzed how Apple synchronizes passkeys. &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=&quot;https://security.googleblog.com/2022/10/SecurityofPasskeysintheGooglePasswordManager.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is worth noting that inter-operability is a hot-topic and vendors are increasingly working on ways to share passkeys across different providers and devices, for example Apple allows exporting passkeys via AirDrop.&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;&lt;a href=&quot;#phishing&quot;&gt;Phishing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#stolen-credentials&quot;&gt;Stolen Credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-end-of-multi-factor-authentication-mfa&quot;&gt;The end of multi-factor authentication (MFA)?&lt;/a&gt;&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=&quot;https://www.slashid.dev/blog/webauthn-antiphishing/&quot;&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.
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=&quot;https://www.w3.org/TR/webauthn/#sctn-credential-loss-key-mobility&quot;&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 Strong Customer Authentication standards (more on this &lt;a href=&quot;https://www.slashid.dev/blog/webauthn-compliance/&quot;&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;&lt;a href=&quot;#secure-key-generation&quot;&gt;Secure key generation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#secure-login&quot;&gt;Secure login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#random-nonces&quot;&gt;Random nonces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#security-flags&quot;&gt;Security flags&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#discoverable-credentials-vs-server-side-credentials&quot;&gt;Discoverable credentials vs server-side credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#adding-new-credentials-to-an-account&quot;&gt;Adding new credentials to an account&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&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=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create&quot;&gt;here&lt;/a&gt;). At a minimum, the server must send a random challenge/nonce, like shows in this example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
user_cred = {
   &apos;user_name&apos;: &apos;example@example.com&apos;,
   &apos;display_name&apos;: &apos;Mr Example’,
   &apos;user_id&apos;: os.urandom(12),
}
challenge = os.urandom(32)
…
return jsonify({
    &apos;credential&apos;: None,
    &apos;displayName&apos;: user_cred[&apos;display_name&apos;],
    &apos;userName&apos;: user_cred[&apos;user_name&apos;],
    &apos;userId&apos;: base64.b64encode(user_cred[&apos;user_id&apos;]).decode(&apos;ascii&apos;),
    &apos;challenge&apos;: base64.b64encode(challenge).decode(&apos;ascii&apos;),
    &apos;relyingPartyName&apos;: &apos;localhost&apos;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The web application will then call &lt;code&gt;navigator.credentials.create&lt;/code&gt; to generate a key pair.
The parameters of the call will look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   credOptions = {
     publicKey: {
       // Relying party
       rp: {
         name: relyingPartyName // &apos;localhost&apos;
       },


       // User parameters
       user: {
         id: userIdBuffer, // os.urandom(12)
         name: userName, //example@example.com
         displayName: params.displayName, //Mr Example
       },


       // This specifies the list of acceptable key types for the authenticator to generate.
       // For most implementations, it’s either ECC (-7) or RSA (-256).
       pubKeyCredParams: [{
         type: &apos;public-key&apos;,
         alg: -7, // ECC
       }],


       // Here we can decide whether to use the current device as the authenticator or whether
       // we should allow the user to select the authenticator (eg: an external hardware security key)
       // ‘platform’ forces the choice of the local device
       authenticatorSelection: {
         authenticatorAttachment: &apos;platform&apos;,
       },


       // This is to avoid replay attacks and needs to be random. It’s the nonce sent by the server
       challenge: challengeBuffer,


       // Ask the authenticator to generate an attestation statement to prove the device type used
       // to generate this key. This doesn’t always work, apple devices will refuse to generate an
       // attestation statement for instance.
       attestation: &apos;direct&apos;,
     }
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;attestation&quot;: &quot;o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEgwRgIhAM19ZXaMJ703tCUuinx9Pqkh+hhKAh0N+nZYufV0SSX1AiEAxxOaRaVchuQDFn8FWnfrR9lbLPR7fHzTlJktbvra5x9oYXV0aERhdGFYpEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjRQAAAACtzgACNbzGCmSLCyXx8FUDACCaZeUU5GyTWfAlwU+D8vq/UsVgjwH1RAt8G6ngG/pyF6UBAgMmIAEhWCBOVpnWqcLp0U6DyQe1roMkSBrTRcir+LcIP1Pa925OhSJYIE+275/X4cNt16Q4hOYj5HN/Qb0vKaEH4p7jtsHfxvJd&quot;,
  &quot;clientData&quot;: &quot;eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiczBqMFVjTU4tVV8zcGdZLTFzeXNqVXhySEVxREJGWmQ2a3RVNnZoeVh0dyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0=&quot;,
  &quot;id&quot;: &quot;mmXlFORsk1nwJcFPg_L6v1LFYI8B9UQLfBup4Bv6chc&quot;, //this value needs to be persisted in the server and associated with the user
  &quot;type&quot;: &quot;public-key&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;clientData&lt;/code&gt; object is a Base64-encode JSON:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;type&quot;: &quot;webauthn.create&quot;,
  &quot;challenge&quot;: &quot;s0j0UcMN-U_3pgY-1sysjUxrHEqDBFZd6ktU6vhyXtw&quot;,
  &quot;origin&quot;: &quot;http://localhost:5000&quot;,
  &quot;crossOrigin&quot;: false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.w3.org/TR/webauthn/#attestation-object&quot;&gt;attestation object&lt;/a&gt; is a Base64-encode &lt;a href=&quot;https://en.wikipedia.org/wiki/CBOR&quot;&gt;CBOR&lt;/a&gt; map. The shape of the attestation object is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
 &quot;attStmt&quot;: {   &apos;alg&apos;: -7,
                &apos;sig&apos;: b&quot;0E\x02R\xa4\xfa\xf0\xb9@\xed\xc3\xeb\xf8\x0f&quot;
                          b&quot;\xd2?*p\xdd\x9b\x11W\xeb\xdd\xf0\x08\xcb4\x16&quot;
                          b&quot;\xa0\x88\xce\x02!\x00\xb4S\x07\xc8pJ\xb9==&quot;
                          b&quot;\x08\xe1Qf\x08\x959O\x8a/H7\x13\xec\x00&quot;
                          b&quot;[\x9e\xcf\x89\xca\xca\xfb&quot;
            },
&quot;authData&quot;: b&quot;I\x96\r\xe5\x88\x0e\x8cht4\x17\x0fdv`[\x8f\xe4\xae\xb9&quot;
                b&quot;\xa2\x862\xc7\x99\\\xf3\xba\x83\x1d\x97cE\x00\x00\x00&quot;
                b&quot;\x00\xad\xce\x00\x025\xbc\xc6\nd\x8b\x0b%\xf1\xf0U&quot;
                b&quot;\x03\x00 \xe2\x98\xb2\xd8\xa5QK\x84\x02\x87\x83/Z&quot;
                b&quot;\xaf\xa3\xf0E\xe1J\n\x99\xe7\xd9\x1cR\x9eE!\xeb\\{i\xa5&quot;
                b&quot;\x01\x02\x03&amp;amp; \x01!X \xbel&amp;gt;^%\x90\x81U\xb7\xe1&amp;amp;\xa9\xda\x19r&quot;
                b&quot;\x9c&amp;lt;4M\x9c\x1dkZ\xe8\x8d\xfb\xdd /\x02\x17\xe7X j&amp;amp;w\xc7&quot;
                b&quot;\xf4\x18C\x8ec\x1e&amp;lt;\x95\xa7\x02\x1e\x18\xf9D\xb0[&quot;
                b&quot;\xde\x11\x912\xf4\xb8\xabX\xe0\xc4\x0eU&quot;,
    &quot;fmt&quot;: &apos;packed&apos;
}
&lt;/code&gt;&lt;/pre&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=&quot;https://www.w3.org/TR/webauthn/#authenticator-data&quot;&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;/p&gt;
&lt;pre&gt;&lt;code&gt;
{
    &quot;attestedCredData&quot;:
         {   &quot;aaguid&quot;: b&quot;\xad\xce\x00\x025\xbc\xc6\nd\x8b\x0b%&quot;
                       b&quot;\xf1\xf0U&quot;,
              &quot;credentialId&quot;: b&quot;\x19\x81\xe5\x989Dq]&quot;
                             b&quot;\x0b\xf5\xc6\xbc]Do\xbb\xd9-~\xac&quot;
                             b&quot;\x81\x8dgS\xea\x08S\x10?\x124\x95&quot;,
              &quot;credentialIdLength&quot;: 32,
              &quot;credentialPublicKey&quot;: {   &quot;alg&quot;: &quot;ecc&quot;,
                                       &quot;eccurve&quot;: &quot;P256&quot;,
                                       &quot;key&quot;: &amp;lt;cryptography.hazmat.backends.openssl.ec._EllipticCurvePublicKey object at 0x103f65b10&amp;gt;,
                                       &quot;kty&quot;: &quot;ECP&quot;,
                                       &quot;x&quot;: b&quot;:\xe2\x0f}&quot;
                                            b&quot;[\x8a\xd2$&quot;
                                            b&quot;\xa2\xcc{.&quot;
                                            b&quot;l\xa4\xf7\xbf&quot;
                                            b&quot;\xe8:\\\x18&quot;
                                            b&quot;\x06\xcbC\x11&quot;
                                            b&quot;K\xbeG\xe5&quot;
                                            b&quot;\x0fL\nY&quot;,
                                       &quot;y&quot;: b&quot;c\xa1\x96@&quot;
                                            b&quot;&amp;gt;e\xa1\xac&quot;
                                            b&quot;\xcfB\xa4\x99&quot;
                                            b&quot;\\\x01\x02\xc0&quot;
                                            b&quot;\xe8\xce\x9e^&quot;
                                            b&quot;#p\x07\xc3&quot;
                                            b&quot;\x86\x07\x04s&quot;
                                            b&quot;\x18P\x00\xa8&quot;}},
    &quot;flags&quot;: {
              &quot;attestedCredDataIncluded&quot;: True,
              &quot;extensionDataIncluded&quot;: False,
              &quot;userPresent&quot;: True,
              &quot;userVerified&quot;: True
         },
   &quot;flagsRaw&quot;: 69,
   &quot;rpIdHash&quot;: b&quot;I\x96\r\xe5\x88\x0e\x8cht4\x17\x0fdv`[\x8f\xe4\xae\xb9&quot;
               b&quot;\xa2\x862\xc7\x99\\\xf3\xba\x83\x1d\x97&quot;,
   &quot;signCount&quot;: 0
}
&lt;/code&gt;&lt;/pre&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=&quot;https://www.w3.org/TR/webauthn/#verification-procedure&quot;&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;/p&gt;
&lt;pre&gt;&lt;code&gt;    return jsonify({
        &apos;challenge&apos;: base64.b64encode(last_challenge).decode(&apos;ascii&apos;),
        &apos;rpId&apos;: &apos;localhost&apos;,
        &apos;userVerification&apos;: &apos;required&apos;,
        &apos;extensions&apos;: None,
        &apos;allowCredentials&apos;: [{
            type: &quot;public-key&quot;,
            id: &quot;AK-r2A2ophbN_fw_xsHxP3z-l-5SVcbZi5Ez9oLgWrY&quot;
        }],
        &apos;timeout&apos;: 60000})
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;id&quot;: &quot;AK-r2A2ophbN_fw_xsHxP3z-l-5SVcbZi5Ez9oLgWrY&quot;,
  &quot;raw_id&quot;: &quot;AK-r2A2ophbN_fw_xsHxP3z-l-5SVcbZi5Ez9oLgWrY&quot;,
  &quot;response&quot;: {
    &quot;client_data_json&quot;: &quot;eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiWGE2RGdfRTB5TFVZelBja05URDBER2pEcHZtX2JKRDhmazZnbHZJUDc3YyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0&quot;,
    &quot;authenticator_data&quot;: &quot;SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAA&quot;,
    &quot;signature&quot;: &quot;MEUCID2sx44Y2iuVdeBZV8BXxeuRGPzsOrbmS0mTCQ6j25SzAiEAwYem5DpkU_oHjVJopmWCSYDUhJpxGtwb7fjZPgZmaKA&quot;,
    &quot;user_handle&quot;: &quot;1EUqUv7528w&quot;
  },
  &quot;authenticator_attachment&quot;: &quot;platform&quot;,
  &quot;type&quot;: &quot;public-key&quot;
}
&lt;/code&gt;&lt;/pre&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=&quot;https://www.w3.org/TR/webauthn/#verification-procedure&quot;&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=&quot;https://www.w3.org/TR/webauthn/#concept-user-present&quot;&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=&quot;https://www.w3.org/TR/webauthn/#concept-user-verified&quot;&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=&quot;https://www.w3.org/TR/webauthn/#attested-credential-data&quot;&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=&quot;https://www.w3.org/TR/webauthn/#test-of-user-presence&quot;&gt;test of user presence&lt;/a&gt; is a simple form of &lt;a href=&quot;https://www.w3.org/TR/webauthn/#authorization-gesture&quot;&gt;authorization gesture&lt;/a&gt; and technical process where a user interacts with an &lt;a href=&quot;https://www.w3.org/TR/webauthn/#authenticator&quot;&gt;authenticator&lt;/a&gt; by (typically) simply touching it (other modalities may also exist), yielding a Boolean result.
Note that this does not constitute  Note that this does not constitute &lt;a href=&quot;https://www.w3.org/TR/webauthn/#user-verification&quot;&gt;user verification&lt;/a&gt; because a &lt;a href=&quot;https://www.w3.org/TR/webauthn/#test-of-user-presence&quot;&gt;user presence test&lt;/a&gt;, by definition, is not capable of &lt;a href=&quot;https://www.w3.org/TR/webauthn/#biometric-recognition&quot;&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
The technical process by which an &lt;a href=&quot;https://www.w3.org/TR/webauthn/#authenticator&quot;&gt;authenticator&lt;/a&gt; locally authorizes the invocation of the &lt;a href=&quot;https://www.w3.org/TR/webauthn/#authenticatormakecredential&quot;&gt;authenticatorMakeCredential&lt;/a&gt; and &lt;a href=&quot;https://www.w3.org/TR/webauthn/#authenticatorgetassertion&quot;&gt;authenticatorGetAssertion&lt;/a&gt; operations. &lt;a href=&quot;https://www.w3.org/TR/webauthn/#user-verification&quot;&gt;User verification&lt;/a&gt; MAY be instigated through various &lt;a href=&quot;https://www.w3.org/TR/webauthn/#authorization-gesture&quot;&gt;authorization gesture&lt;/a&gt; modalities; for example, through a touch plus pin code, password entry, or &lt;a href=&quot;https://www.w3.org/TR/webauthn/#biometric-recognition&quot;&gt;biometric recognition&lt;/a&gt; (e.g., presenting a fingerprint) &lt;a href=&quot;https://www.w3.org/TR/webauthn/#biblio-isobiometricvocabulary&quot;&gt;ISOBiometricVocabulary&lt;/a&gt;.
The intent is to distinguish individual users.
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.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.
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;/p&gt;
&lt;pre&gt;&lt;code&gt;    return jsonify({
        &apos;challenge&apos;: base64.b64encode(last_challenge).decode(&apos;ascii&apos;),
        &apos;rpId&apos;: site_config[&apos;relying_party_name&apos;],
        &apos;userVerification&apos;: &apos;required&apos;,
        &apos;extensions&apos;: None,
        &apos;timeout&apos;: 60000})
&lt;/code&gt;&lt;/pre&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&apos;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=&quot;https://github.com/slashid/website-passkeys-playground&quot;&gt;GitHub&lt;/a&gt; and &lt;a href=&quot;https://passkeys-playground.slashid.dev/&quot;&gt;live&lt;/a&gt;.&lt;/p&gt;
</content:encoded><author>SlashID Team, Vincenzo Iozzo</author></item><item><title>Navigating PCI DSS 4.0: The Challenge of Non-Human Identities</title><link>https://slashid.dev/blog/pci-dss-nhi/</link><guid isPermaLink="true">https://slashid.dev/blog/pci-dss-nhi/</guid><description>The Payment Card Industry Data Security Standard (PCI DSS) has long served as the foundation for organizations handling payment card data, ensuring robust security measures are - in place to protect sensitive information  The release of PCI DSS version 4.0 on March 31, 2022, marked a significant evolution in the standard, introducing requirements and emphasizing areas that were previously under-addressed.  One such critical area is the management of non-human identities—service accounts, application accounts, APIs, and automated scripts that interact with cardholder data environments (CDE) or critical systems.  With the deadline of March 2025 fast approaching, we wrote a blog post to delves into the specific challenges companies face regarding non-human identities in PCI DSS v4.0 and - explores strategies to overcome them.</description><pubDate>Mon, 16 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Understanding Non-Human Identity Requirements in PCI DSS v4.0&lt;/h2&gt;
&lt;p&gt;PCI DSS v4.0 introduces several new mandates aimed at enhancing the security of non-human identities:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Unique Identification:&lt;/strong&gt; Each non-human entity must have a unique ID to ensure accountability (Requirement 8.2.2).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure Authentication:&lt;/strong&gt; Strong authentication methods must be implemented, avoiding hard-coded passwords (Requirement 8.6).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credential Management:&lt;/strong&gt; Authentication credentials must be securely managed and rotated regularly (Requirement 8.3.5).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Least Privilege Access:&lt;/strong&gt; Access rights should be limited to the minimum necessary (Requirement 7.1).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regular Review and Deprovisioning:&lt;/strong&gt; Access rights must be periodically reviewed, and unnecessary accounts removed (Requirements 7.2.5 and 8.1.4).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitoring and Logging:&lt;/strong&gt; Activities of non-human accounts must be logged and monitored (Requirement 10.2.1).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure Transmission:&lt;/strong&gt; Credentials must be transmitted using strong cryptography (Requirement 8.5.1).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cryptographic Key Protection:&lt;/strong&gt; Cryptographic keys must be securely managed (Requirement 3.5).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoid Hard-Coded Credentials:&lt;/strong&gt; Hard-coded passwords in code or scripts are prohibited (Requirement 8.6.1).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Segregation of Duties:&lt;/strong&gt; Non-human accounts must not have conflicting responsibilities (Requirement 10.4.1).&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;The Challenges Companies Are Facing&lt;/h2&gt;
&lt;h3&gt;1. Identifying and Managing Non-Human Accounts&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Many organizations lack a comprehensive inventory of non-human accounts, which proliferate in complex IT environments.
&lt;strong&gt;Impact:&lt;/strong&gt; Without unique identification, accountability and traceability are difficult, increasing risks of unauthorized access and non-compliance.&lt;/p&gt;
&lt;h3&gt;2. Eliminating Hard-Coded Credentials&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Hard-coded credentials in scripts and applications are commonly used for convenience but pose significant security risks.
&lt;strong&gt;Impact:&lt;/strong&gt; Embedded credentials are prone to exposure, violating PCI DSS requirements.&lt;/p&gt;
&lt;h3&gt;3. Implementing Strong Authentication Methods&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Legacy systems may not support modern authentication mechanisms like API tokens or certificates.
&lt;strong&gt;Impact:&lt;/strong&gt; Outdated methods weaken security and hinder compliance.&lt;/p&gt;
&lt;h3&gt;4. Secure Credential Management and Rotation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Manual credential management is time-consuming and error-prone.
&lt;strong&gt;Impact:&lt;/strong&gt; Infrequent rotation and insecure storage increase breach risks.&lt;/p&gt;
&lt;h3&gt;5. Enforcing Least Privilege Access&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Non-human accounts often have broad permissions for operational ease.
&lt;strong&gt;Impact:&lt;/strong&gt; Over-privileged access increases risks and violates the principle of least privilege.&lt;/p&gt;
&lt;h3&gt;6. Regular Review and Deprovisioning of Accounts&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Tracking access rights for all non-human accounts is difficult without automation.
&lt;strong&gt;Impact:&lt;/strong&gt; Orphaned or unnecessary accounts create vulnerabilities.&lt;/p&gt;
&lt;h3&gt;7. Comprehensive Monitoring and Logging&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Existing logging systems may not capture non-human account activities across platforms.
&lt;strong&gt;Impact:&lt;/strong&gt; Insufficient monitoring delays incident detection and response.&lt;/p&gt;
&lt;h3&gt;8. Secure Transmission of Credentials&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Ensuring secure credential transmission in mixed legacy and modern environments is challenging.
&lt;strong&gt;Impact:&lt;/strong&gt; Unsecured transmission can result in breaches and compliance failures.&lt;/p&gt;
&lt;h3&gt;9. Protecting Cryptographic Keys&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Securely managing cryptographic keys throughout their lifecycle requires specialized tools.
&lt;strong&gt;Impact:&lt;/strong&gt; Compromised keys can lead to unauthorized decryption of sensitive data.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Strategies for Achieving Compliance&lt;/h2&gt;
&lt;h3&gt;Conduct a Comprehensive Audit&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inventory All Non-Human Accounts:&lt;/strong&gt; Use automation to identify service accounts, application accounts, and APIs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assess Current Practices:&lt;/strong&gt; Evaluate how credentials are managed and used.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Implement Automated Solutions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Non-Human Identity Technology:&lt;/strong&gt; Automate inventory creation, detect over-privileged accounts, and manage credentials securely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credential Rotation:&lt;/strong&gt; Enforce regular rotation via dashboards to prevent disruptions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Upgrade Authentication Mechanisms&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Adopt Strong Authentication:&lt;/strong&gt; Transition to API keys, tokens, or certificates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ensure Compatibility:&lt;/strong&gt; Use solutions that work with modern and legacy systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Enforce Least Privilege Access&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Review and Adjust Permissions:&lt;/strong&gt; Regularly remove over-privileged accounts based on inventory data.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Enhance Monitoring and Logging&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Centralized Logging Systems:&lt;/strong&gt; Capture detailed activities from non-human accounts across environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrate with SIEM:&lt;/strong&gt; Use Security Information and Event Management tools for real-time anomaly detection.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Secure Communication Channels&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enforce Encryption Protocols:&lt;/strong&gt; Use strong encryption (e.g., TLS 1.2+).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Isolate Legacy Systems:&lt;/strong&gt; Implement compensating controls for systems that cannot be upgraded.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Develop and Enforce Policies&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Non-Human Identity Policies:&lt;/strong&gt; Define lifecycle management for non-human accounts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Training and Awareness:&lt;/strong&gt; Educate teams on securing non-human identities and compliance practices.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;The Importance of Immediate Action&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Key Steps to Take Now:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Set a Compliance Timeline:&lt;/strong&gt; Break tasks into phases with clear deadlines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Allocate Resources:&lt;/strong&gt; Secure budget and personnel for implementation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Engage Stakeholders:&lt;/strong&gt; Involve IT, security, compliance, and management.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor Progress:&lt;/strong&gt; Regularly review and adjust the plan.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Select a Technology Partner:&lt;/strong&gt; Automate detection and inventory tasks to expedite compliance.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Meeting PCI DSS v4.0 requirements for non-human identities is complex but essential. A strategic approach—combining audits, automated tools, and robust policies—bridges the technology gaps and strengthens security. By prioritizing these efforts, organizations can not only achieve compliance but also bolster their overall security posture.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;About SlashID&lt;/h2&gt;
&lt;p&gt;At SlashID, we simplify identity management while enhancing security. Our expertise and tools help organizations navigate PCI DSS compliance challenges effectively. For tailored solutions, contact us to support your journey toward PCI DSS v4.0 compliance.&lt;/p&gt;
</content:encoded><author>Will Easton, Vincenzo Iozzo</author></item><item><title>SlashID RBAC: Globally-available role-based access control </title><link>https://slashid.dev/blog/rbac/</link><guid isPermaLink="true">https://slashid.dev/blog/rbac/</guid><description>SlashID RBAC is a globally replicated role-based access control system that allows you to restrict access to resources based on permissions assigned to specific persons.  In this post, we will show you how to use RBAC in SlashID, and how to create permissions, and roles, and assign them to persons.</description><pubDate>Mon, 22 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Why do you need RBAC?&lt;/h2&gt;
&lt;p&gt;Most products need to have a way to restrict access to certain operations.
A standard way to do this is to use Role-Based Access Control (RBAC).
RBAC is a method of restricting resource access based on permissions assigned to specific persons.&lt;/p&gt;
&lt;p&gt;In SlashID there are two ways to assign permissions to a person:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;assigning a permission directly to a person,&lt;/li&gt;
&lt;li&gt;assigning permission to a role and then assigning the role to a person.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/blog/rbac/entities.png&quot; alt=&quot;Entities&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;How does RBAC work in SlashID&lt;/h2&gt;
&lt;p&gt;Our goal was to create state-of-the-art RBAC implementation in SlashID.
We did it by giving you features not available in other RBAC implementations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Our RBAC implementation is globally replicated - roles, permissions, and assignments are available in all regions, you can check permissions in any geographical location with minimal latency,&lt;/li&gt;
&lt;li&gt;it&apos;s compatible with our &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/replication&quot;&gt;replication model&lt;/a&gt;, where you can control write consistency,&lt;/li&gt;
&lt;li&gt;it works with our &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/suborgs&quot;&gt;suborganizations model&lt;/a&gt;, so you can share roles and permissions between organizations within the organization tree,&lt;/li&gt;
&lt;li&gt;all write operations emit events, to which you can &lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/webhooks/introduction&quot;&gt;subscribe with webhooks&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&apos;s now dive into how to use RBAC in SlashID.&lt;/p&gt;
&lt;p&gt;In this post, I will show you how to use RBAC in SlashID, and how to create permissions, and roles, and assign them to persons.
I&apos;ll use an example of a billing system where we have two roles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;/accountant&lt;/code&gt; with permissions &lt;code&gt;billing.invoices.list&lt;/code&gt;, &lt;code&gt;billing.invoices.create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;/administrator&lt;/code&gt; with permissions &lt;code&gt;billing.invoices.list&lt;/code&gt;, &lt;code&gt;billing.invoices.create&lt;/code&gt;, &lt;code&gt;billing.invoices.void&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;where &lt;code&gt;&amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;&lt;/code&gt; is the ID of the organization.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: Organization ID is part of the role name to support the &lt;a href=&quot;#multi-org-support&quot;&gt;multi-organization model&lt;/a&gt; for RBAC.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this scenario, persons with the &lt;code&gt;&amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;/accountant&lt;/code&gt; role will have access to read and create invoices,
while persons with the &lt;code&gt;&amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;/administrator&lt;/code&gt; role can read, create, and void invoices.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/rbac/roles.png&quot; alt=&quot;Roles&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Creating permissions&lt;/h3&gt;
&lt;p&gt;You can create permissions with the &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-rbac-permissions&quot;&gt;&lt;code&gt;POST /rbac/permissions&lt;/code&gt; endpoint&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X POST &apos;https://api.slashid.com/rbac/permissions&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
--data-raw &apos;{
 &quot;name&quot;: &quot;billing.invoices.list&quot;,
 &quot;description&quot;: &quot;This role allows to list invoices&quot;
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each endpoint allows you to specify the &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/replication#organization-data---global-replication-in-depth&quot;&gt;required regional consistency&lt;/a&gt; of the request.
By default, the request will update the region to which the request was sent. Other regions will be updated asynchronously to achieve eventual consistency through our replication model.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: If you want to learn more about our replication model, please visit our &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/replication&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Creating roles&lt;/h3&gt;
&lt;p&gt;After creating permissions, you can create roles with the &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-rbac-roles&quot;&gt;&lt;code&gt;POST /rbac/roles&lt;/code&gt; endpoint&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X POST &apos;https://api.slashid.com/rbac/roles&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
--data-raw &apos;{
 &quot;name&quot;: &quot;&amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;/accountant&quot;,
 &quot;description&quot;: &quot;This role is assigned to accountants&quot;,
 &quot;permissions&quot;: [
 &quot;billing.invoices.list&quot;,
 &quot;billing.invoices.create&quot;
 ]
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Assigning roles and permissions to a person&lt;/h3&gt;
&lt;p&gt;After creating roles and permissions, you can assign them to a person with the &lt;a href=&quot;https://developer.slashid.dev/docs/api/put-persons-person-id-roles&quot;&gt;&lt;code&gt;PUT /persons/:person_id/roles&lt;/code&gt; endpoint&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X PUT &apos;https://api.slashid.com/persons/:person_id/roles&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
--data-raw &apos;{
 &quot;roles&quot;: [
 &quot;&amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;/accountant&quot;
 ]
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also assign additional permissions with the &lt;a href=&quot;https://developer.slashid.dev/docs/api/put-persons-person-id-permissions&quot;&gt;&lt;code&gt;PUT /persons/:person_id/additional-permissions&lt;/code&gt; endpoint&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X PUT &apos;https://api.slashid.com/persons/:person_id/additional-permissions&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
--data-raw &apos;{
 &quot;permissions&quot;: [
 &quot;billing.invoices.list&quot;
 ]
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Verifying permissions&lt;/h3&gt;
&lt;p&gt;You can check if a person has specific permission with &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-rbac-check&quot;&gt;&lt;code&gt;POST /rbac/check&lt;/code&gt; endpoint&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X POST &apos;https://api.slashid.com/rbac/check&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
--data-raw &apos;{
 &quot;person_id&quot;: &quot;064b7b63-ea43-76e5-b208-1900795bc5b7&quot;,
 &quot;permission_name&quot;: &quot;billing.invoices.list&quot;
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: &lt;code&gt;/rbac/check&lt;/code&gt; endpoint is transitive. It checks both permissions directly assigned to the user and permissions inherited through roles.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Finding all persons with a specific role&lt;/h3&gt;
&lt;p&gt;You can use &lt;a href=&quot;https://developer.slashid.dev/docs/api/get-persons&quot;&gt;&lt;code&gt;GET /persons&lt;/code&gt; endpoint&lt;/a&gt; to filter persons by role or permission with filters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;roles eq &quot;&amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;/accountant&quot;&lt;/code&gt; - find all persons with role &lt;code&gt;&amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;/accountant&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;permissions eq &quot;billing.invoices.list&quot;&lt;/code&gt; - find all persons with permission &lt;code&gt;billing.invoices.list&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X GET &apos;https://api.slashid.com/persons?filter=roles%20eq%20%22a4ed1ac1-b89c-3b8e-4752-f121fed331a0%2Faccountant%22&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: Please remember to URL encode the filter parameter.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For the entire &lt;code&gt;GET /persons&lt;/code&gt; specification, please visit our &lt;a href=&quot;https://developer.slashid.dev/docs/api/get-persons&quot;&gt;API documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Multi-org support&lt;/h3&gt;
&lt;p&gt;By default, roles and permissions are isolated within the organization tree.
You can share roles and permissions between sub-organizations by enabling the &lt;code&gt;inherit_rbac_pools&lt;/code&gt; feature when &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-organizations-suborganizations&quot;&gt;creating a suborganization&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/rbac/pools.png&quot; alt=&quot;Entities&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When you enable the &lt;code&gt;inherit_rbac_pools&lt;/code&gt; feature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Suborganizations can&apos;t add new permissions - they can only use permissions from the first parent who has &lt;code&gt;inherit_rbac_pools&lt;/code&gt; set to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Suborganizations can add new roles and assign them to persons in the sub-organization. Roles created in the sub-organization can use permissions from the parent organization.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Adding permissions to a token&lt;/h3&gt;
&lt;p&gt;You can add permissions to a token by using &lt;a href=&quot;https://developer.slashid.dev/docs/api/get-organizations-config-token-template&quot;&gt;token templating functionality&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L -X PUT &apos;https://api.slashid.com/organizations/config/token-template&apos; \
-H &apos;Content-Type: application/json&apos; \
-H &apos;Accept: application/json&apos; \
-H &apos;SlashID-OrgID: &amp;lt;SLASHID_ORG_ID_VALUE&amp;gt;&apos; \
-H &apos;SlashID-API-Key: &amp;lt;API_KEY_VALUE&amp;gt;&apos; \
--data-raw &apos;{
&quot;content&quot;: &quot;{ \&quot;permissions\&quot;: {{ person.permissions }} }&quot;
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;You can find specifications for our RBAC endpoints in our &lt;a href=&quot;https://developer.slashid.dev/docs/category/api/rbac&quot;&gt;API documentation&lt;/a&gt;.
You can also use our latest &lt;a href=&quot;https://cdn.slashid.com/slashid-openapi-latest.yaml&quot;&gt;OpenAPI specification&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Don&apos;t hesitate to &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;reach out to us&lt;/a&gt; or sign up for free &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=rbac&quot;&gt;here&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Robert Laszczak, Vincenzo Iozzo</author></item><item><title>Sign-in and Sign-up React component release</title><link>https://slashid.dev/blog/react-component-signup/</link><guid isPermaLink="true">https://slashid.dev/blog/react-component-signup/</guid><description>Today we’re happy to announce the next step in that journey to deliver a streamlined, low friction onboarding experience to our customers with the release of our sign-up/sign-in form component.</description><pubDate>Wed, 18 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;As mentioned in a &lt;a href=&quot;https://www.slashid.dev/blog/react-sdk-v1/&quot;&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 start using the components right away.&lt;/p&gt;
&lt;p&gt;If you are not a customer yet, we&apos;d love to give you a test account to try it out! Please send us an email at &lt;code&gt;hello@slashid.dev&lt;/code&gt; or &lt;a href=&quot;https://console.slashid.dev/signup&quot;&gt;sign up&lt;/a&gt; to get started. The source code for our React components is available in our &lt;a href=&quot;https://github.com/slashid/javascript/tree/main/packages/react/src/components&quot;&gt;GitHub repository&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;/p&gt;
&lt;p&gt;&lt;video style=&quot;width:100%&quot; autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/blog/react-signup-components/video1.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&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=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties&quot;&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.
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;video style=&quot;width:100%&quot; autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/blog/react-signup-components/video2.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&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;
</content:encoded><author>Ivan Kovic, Vincenzo Iozzo</author></item><item><title>Authentication flows with SlashID</title><link>https://slashid.dev/blog/react-mfa/</link><guid isPermaLink="true">https://slashid.dev/blog/react-mfa/</guid><description>Implement MFA and Step-Up Authentication in React applications with SlashID.</description><pubDate>Fri, 12 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;​
In today&apos;s fast-paced digital world, security is more important than ever. With so many authentication options available, like Single Sign-On (SSO), biometrics and social media logins, it can be tough to choose the best one for your app and strike a balance between UX and security.&lt;/p&gt;
&lt;p&gt;Flexible Multi-Factor Authentication and Step-Up Authentication are useful tools to fight threats like account takeover and fraud while maintaining a good user experience.
​
In this blog post, we will show you how to integrate MFA and Step-Up Authentication in React applications with SlashID.
​&lt;/p&gt;
&lt;h2&gt;Why choose SlashID?&lt;/h2&gt;
&lt;p&gt;​
Before we dive deeper, here’s a couple of reasons why you should give us a shot:
​&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Out-of-the-box experience&lt;/strong&gt; - with our UI components you don’t have to worry about implementing complex auth flows, such as MFA or Step-Up Authentication. For each of these flows, we have a configurable React component, ready to be used in your application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent, flexible API&lt;/strong&gt; - each of our authentication flows is built on top of the same configuration API, so you can seamlessly switch between the various auth solutions - whichever fits your needs!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extension by composition&lt;/strong&gt; - our higher-level components are built using the lower-level APIs, such as LoggedIn / LoggedOut components for conditional UI rendering. Contrary to other solutions in the market, our approach doesn&apos;t require hooks and it&apos;s easy to customize. You can implement your own custom authentication flow using these bits!
​&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, let’s see how all these apply in the real world examples!
​&lt;/p&gt;
&lt;h2&gt;Multi-Factor Authentication&lt;/h2&gt;
&lt;p&gt;​
MFA offers a powerful security solution by requiring users to verify their identity with two or more factors, such as passwords, security tokens, or biometrics, before accessing an application or sensitive data. At SlashID we believe in a fully passwordless future and encourage users to choose methods like email magic links or passkeys as their primary authentication factors.
​&lt;/p&gt;
&lt;p&gt;Implementing MFA in your React application not only elevates security, but also reduces user friction. You can think of it as a tradeoff between safeguarding sensitive information and providing a user-friendly experience. The users aren&apos;t overwhelmed by strict security measures for every interaction, while critical data and high-risk resources receive the necessary protection.
​&lt;/p&gt;
&lt;h2&gt;Introducing the &lt;code&gt;&amp;lt;MultiFactorAuth&amp;gt;&lt;/code&gt; component&lt;/h2&gt;
&lt;p&gt;​
&lt;code&gt;&amp;lt;MultiFactorAuth&amp;gt;&lt;/code&gt; is SlashID’s MFA component, which offers a first-class MFA experience. You can specify the number of steps and authentication factors required at each step as a code configuration:
​&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;MultiFactorAuth
  steps={[
    { factors: [{ method: &apos;email_link&apos; }] }, // first factor: email magic link
    { factors: [{ method: &apos;otp_via_sms&apos; }] }, // second factor: SMS OTP code
  ]}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​
Notice how this is similar to the &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-form#props&quot;&gt;&lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; component configuration&lt;/a&gt;; in fact, &lt;code&gt;&amp;lt;MultiFactorAuth&amp;gt;&lt;/code&gt; uses it under the hood!
​&lt;/p&gt;
&lt;p&gt;&lt;video style=&quot;width:100%&quot; autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/blog/react-mfa/mfa_1080.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&gt;

&lt;/p&gt;
&lt;p&gt;​
With &lt;code&gt;&amp;lt;MultiFactorAuth&amp;gt;&lt;/code&gt; you can effortlessly transition from a Single-Factor Authentication to MFA. The component takes care of the verification logic and allows you to select any number of factors for MFA.
​&lt;/p&gt;
&lt;h2&gt;Step-Up Authentication&lt;/h2&gt;
&lt;p&gt;​
Step-Up Authentication is a dynamic security measure that takes into account the risk associated with specific actions or access to sensitive data within an application. Instead of enforcing a uniform level of security for all user interactions, Step-Up Authentication allows for a more granular approach, increasing the level of required authentication only for high-risk resources or sensitive data access.&lt;/p&gt;
&lt;p&gt;​
By adopting Step-Up Authentication, you can ensure a balance between usability and security. Users are allowed to access low-risk resources with their standard authentication credentials, like email magic links. However, when they attempt to access high-risk resources or interact with sensitive data (e.g., payment information), the system prompts them for additional authentication factors, such as a one-time password (OTP) sent to their mobile device, biometric data, or a physical security token.&lt;/p&gt;
&lt;p&gt;​
Integrating Step-Up Authentication into your React application not only enhances security but also reduces user friction. Users are not burdened with high-level security measures for every interaction within the app, making the overall experience more user-friendly. Meanwhile, you can rest assured that critical data and high-risk resources remain protected with an added layer of security when needed.
​&lt;/p&gt;
&lt;h2&gt;Introducing the &lt;code&gt;&amp;lt;StepUpAuth&amp;gt;&lt;/code&gt; component&lt;/h2&gt;
&lt;p&gt;​
&lt;code&gt;&amp;lt;StepUpAuth&amp;gt;&lt;/code&gt; is similar to the other authentication flow components available within the React SDK. It consists of a &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; and a lower-level &lt;code&gt;&amp;lt;LoggedIn&amp;gt;&lt;/code&gt; component, which you can learn more about in our &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/&quot;&gt;documentation&lt;/a&gt;.
​&lt;/p&gt;
&lt;p&gt;A standout feature of this component is the ability to utilize the &lt;code&gt;onSuccess&lt;/code&gt; callback function. This allows you to execute sensitive operations upon successful factor verification:
​&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;StepUpAuth
  factors={[{ method: &apos;otp_via_sms&apos; }]}
  onSuccess={() =&amp;gt; {
    /* your sensitive operation… */
  }}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​
And that&apos;s all there is to it – your code remains neat, well-organized, and consistent! By leveraging SlashID React SDK, you can effortlessly integrate robust security measures into your application, without compromising on user experience.
​&lt;/p&gt;
&lt;p&gt;&lt;video style=&quot;width:100%&quot; autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/blog/react-mfa/step_up_auth_1080.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&gt;

&lt;/p&gt;
&lt;p&gt;​
The &lt;code&gt;&amp;lt;StepUpAuth&amp;gt;&lt;/code&gt; component empowers you to protect sensitive operations or assets within your application.
​&lt;/p&gt;
&lt;h2&gt;Try it yourself&lt;/h2&gt;
&lt;p&gt;​
If you enjoyed reading this blog post, give us a go and &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=reactmfa-bp&quot;&gt;sign up&lt;/a&gt; to use the SlashID SDKs today! Remember to check out the docs – &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/tutorials/react-sdk-tutorials-quickstart&quot;&gt;React Quickstart tutorial&lt;/a&gt; is a good entry point for getting started with our platform. We also have a dedicated &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/tutorials/react-sdk-tutorials-mfa&quot;&gt;MFA tutorial&lt;/a&gt; in case you want to get down to business immediately!
​&lt;/p&gt;
&lt;p&gt;Multi-Factor and Step-Up Authentication are just the beginning: in the future, we plan to enable more advanced and personalized security features, such as Adaptive or Risk-Based Authentication. Stay tuned if you don’t want to miss out – let’s build a secure next generation of the Web, together!&lt;/p&gt;
</content:encoded><author>Kasper Mroz, Vincenzo Iozzo</author></item><item><title>Official React SDK release</title><link>https://slashid.dev/blog/react-sdk-v1/</link><guid isPermaLink="true">https://slashid.dev/blog/react-sdk-v1/</guid><description>Today we’re excited to announce the public release of the official SlashID React SDK In this blog post we’ll go over the design pillars, main features, and why we’re thrilled about what’s coming next.</description><pubDate>Mon, 28 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Design pillars&lt;/h2&gt;
&lt;p&gt;Built on top of our core &lt;a href=&quot;https://developer.slashid.dev/docs/access/sdk&quot;&gt;JavaScript SDK&lt;/a&gt;, the &lt;a href=&quot;https://github.com/slashid/javascript/tree/main/packages/react&quot;&gt;React SDK&lt;/a&gt; provides an idiomatic way of using SlashID in React apps.&lt;/p&gt;
&lt;p&gt;Our goal is to produce SDKs that are not only effective in solving common authentication problems, but also provide a great user &amp;amp; developer experience. Having analyzed hundreds of libraries in the JS ecosystem and decided what we liked &amp;amp; disliked about them, we set our design pillars:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TypeScript first&lt;/strong&gt; - All of our client side SDKs are written in TypeScript so the developers can get a helping hand from the compiler&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server side rendering (SSR) friendly&lt;/strong&gt; - SDKs should not prevent or obstruct SSR&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt; - Prefer solutions with the least impact to the application performance. Our SDK is modular and only ships the minimum features necessary for the modules enabled by our customer to reduce load time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt; - Support developers by exposing intuitive APIs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accessibility (a11y)&lt;/strong&gt; - Design the UI components with a11y in mind&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composability&lt;/strong&gt; - Use the same APIs we expose to our customers to build the higher level components&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Features&lt;/h2&gt;
&lt;p&gt;While the core SDK is stateless, the React SDK will keep track of authentication state for you. We provide the hook &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/reference/hooks/react-sdk-reference-useslashid&quot;&gt;&lt;code&gt;useSlashID&lt;/code&gt;&lt;/a&gt; which gives you access to the current &lt;a href=&quot;https://developer.slashid.dev/docs/access/sdk/classes/User/&quot;&gt;&lt;code&gt;User&lt;/code&gt;&lt;/a&gt; instance and the functions for logging in &amp;amp; out. Any change to the &lt;code&gt;user&lt;/code&gt; value will make the components using the hook render again with the new value.&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe src=&quot;https://codesandbox.io/embed/hopeful-austin-486cco?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark&amp;amp;view=editor&quot;
style=&quot;width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;&quot;
title=&quot;hopeful-austin-486cco&quot;
allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot;
sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This hook is fundamental for the two important use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Brand-focused customers can use this hook to build customized user interfaces (UIs) on top of it&lt;/li&gt;
&lt;li&gt;We will use it internally to build stateful authentication components&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This corresponds to the composability design pillar - by offering the low-level API first we empower customers to quickly and easily build their authentication UI, without interfering with their branding &amp;amp; design. With this foundation we can build various abstractions to take the developer experience to the next level. Our control components are a good example of the first step in that direction.&lt;/p&gt;
&lt;h3&gt;Control components&lt;/h3&gt;
&lt;p&gt;The first release of the React SDK also includes our first set of control components - &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-loggedin&quot;&gt;&lt;code&gt;&amp;lt;LoggedIn&amp;gt;&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-loggedout&quot;&gt;&lt;code&gt;&amp;lt;LoggedOut&amp;gt;&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These components will help you conditionally render the UI based on the SDK readiness and the user authentication states. Of course that’s just the start - we’re really excited about what comes next so let’s talk about that briefly.&lt;/p&gt;
&lt;h2&gt;What’s next&lt;/h2&gt;
&lt;p&gt;Following our design pillars and especially leaning on the pillar of composability, we’ll continue building our library of control components to act as a basis for the next level - our pre-made, customizable UI components.&lt;/p&gt;
&lt;p&gt;Our goal is to implement common authentication and session management use cases as high level UI components. Doing this while adhering to the design pillars is an interesting engineering challenge so stay tuned as we release more blog posts on this topic as we go!&lt;/p&gt;
</content:encoded><author>Ivan Kovic, Vincenzo Iozzo</author></item><item><title>Introducing the SlashID Remix SDK: Authentication made easy</title><link>https://slashid.dev/blog/remix-sdk-v1/</link><guid isPermaLink="true">https://slashid.dev/blog/remix-sdk-v1/</guid><description>We’re excited to announce first-party Remix support in SlashID with @slashid/remix. We&apos;ve borrowed the power of our React SDK and aligned it with Remix&apos;s unique design patterns.  The Remix SDK makes authentication for Remix easy and seamless.</description><pubDate>Tue, 02 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&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&apos;ve borrowed the power of our React SDK and aligned it with Remix&apos;s unique design patterns.&lt;/p&gt;
&lt;p&gt;We&apos;ve built the &lt;a href=&quot;https://github.com/slashid/javascript/tree/main/packages/remix#readme&quot;&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;p&gt;Check out our &lt;a href=&quot;https://developer.slashid.dev/docs/access/remix-sdk/tutorials/quick-start&quot;&gt;quick start guide&lt;/a&gt;, add authentication to your Remix application in &lt;a href=&quot;https://developer.slashid.dev/docs/access/remix-sdk/tutorials/quick-start&quot;&gt;six easy steps&lt;/a&gt;.&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 optimize 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=&quot;https://developer.slashid.dev/docs/access/guides/suborgs/multitenancy-example#groups&quot;&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&apos;ve got you covered.&lt;/p&gt;
&lt;p&gt;Not to your taste? Our form appearance can be completely customized - and we mean *completely*. Check out &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-form#ui-customization&quot;&gt;Form: CSS custom properties&lt;/a&gt;, and &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk/reference/components/react-sdk-reference-form#layout-slots--primitives&quot;&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;Conclusion&lt;/h2&gt;
&lt;p&gt;In this brief post we&apos;ve introduced you to &lt;code&gt;@slashid/remix&lt;/code&gt;, the SlashID Remix SDK. Eager to experiment with Remix? We&apos;ve made our SDK quick &amp;amp; easy to implement so you can get productive, fast.&lt;/p&gt;
&lt;p&gt;Get started today with a &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=remixbp&quot;&gt;free account&lt;/a&gt;, and learn how you can implement the SlashID Remix SDK in six easy steps with our &lt;a href=&quot;https://developer.slashid.dev/docs/access/remix-sdk/tutorials/quick-start?utm_source=remixbp&quot;&gt;quick start guide&lt;/a&gt;.&lt;/p&gt;
</content:encoded><author>Jake Whelan, Vincenzo Iozzo</author></item><item><title>Scattered Spider Tradecraft: Identity Abuse, Attack Flow, and Defense</title><link>https://slashid.dev/blog/scattered-spider-tradecraft/</link><guid isPermaLink="true">https://slashid.dev/blog/scattered-spider-tradecraft/</guid><description>Scattered Spider is a highly adaptive cybercrime group that breaches enterprises by abusing identity, trust, and legitimate access paths rather than malware exploits.  Learn about their identity-centric attack flow, real-world campaigns like the MGM and Caesars breaches, and how to defend against these sophisticated threats.</description><pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Scattered Spider is a highly adaptive, human-operated cybercrime group known for breaching enterprises not only through malware exploits, but by abusing identity, trust, and legitimate access paths. Active since at least 2022, the group is loosely organized, English-speaking, and heavily reliant on social engineering rather than technical zero-days. Their operations frequently involve convincing employees, contractors, or helpdesk staff to reset credentials, enroll new MFA methods, or grant access, allowing the attackers to simply log in as valid users.&lt;/p&gt;
&lt;p&gt;What sets Scattered Spider apart is their deep understanding of enterprise identity environments. They target cloud identity providers, SSO platforms, VPNs, and privileged access workflows, often chaining together helpdesk abuse, MFA fatigue, SIM swapping, and token theft to move laterally across identity planes. Once inside, they escalate privileges, disable security controls, and pivot into SaaS, IaaS, and on-prem environments using legitimate tools and sessions. Because their activity closely resembles normal user behavior, traditional perimeter security, endpoint protection, and even MFA often fail to detect them in time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In short, Scattered Spider don&apos;t hack – they Log In.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;They exploit how modern organizations manage identity, trust users, and automate access. This makes them one of the clearest examples of why identity has become the primary attack surface in modern enterprises.&lt;/p&gt;
&lt;h2&gt;Attack Flow&lt;/h2&gt;
&lt;p&gt;Scattered Spider operations typically follow a repeatable, identity-centric attack flow rather than a traditional exploit-driven kill chain.&lt;/p&gt;
&lt;p&gt;Each phase builds on legitimate access, trusted identity workflows, and normal administrative behavior—allowing the attacker to progress from initial compromise to full mission completion without triggering conventional security controls.&lt;/p&gt;
&lt;p&gt;Understanding this flow is critical, because visibility gaps at any phase enable the intrusion to quietly advance across cloud, SaaS, and hybrid identity environments.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/scattered-spider-tradecraft/infographic-1.png&quot; alt=&quot;Scattered Spider Tradecraft&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Scattered Spider TTPs&lt;/h2&gt;
&lt;h3&gt;Initial Compromise&lt;/h3&gt;
&lt;p&gt;Techniques used to gain first access without exploiting software vulnerabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SMS and voice phishing&lt;/strong&gt; – vishing (T1598.004) targeting employees and contractors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Helpdesk impersonation&lt;/strong&gt; (T1656)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MFA fatigue&lt;/strong&gt; (T1621)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SIM swapping&lt;/strong&gt; (T1451)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Establish Foothold&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Compromised credentials&lt;/li&gt;
&lt;li&gt;VPN or SSO access using legitimate accounts (TA0007)&lt;/li&gt;
&lt;li&gt;Abuse of conditional access exceptions or legacy authentication paths&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Maintain Persistence&lt;/h3&gt;
&lt;p&gt;Techniques used to retain access even if credentials are rotated (T1219):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remote access legitimate software such as Ngrok (S0508), TeamViewer, AnyDesk, RustDesk, FleetDeck, Level.io, Teleport.sh&lt;/li&gt;
&lt;li&gt;Malware such as AveMaria (S0670), RattyRat&lt;/li&gt;
&lt;li&gt;Create accessible VMs (T1578.002) using Tailscale, then dump the AD database ntds.dit on these systems&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Escalate Privilege&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Enumeration of roles, groups, and delegated permissions&lt;/li&gt;
&lt;li&gt;Malware such as Mimikatz (S0002), RacoonStealer (S1148)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Internal Reconnaissance&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Enumeration of users, groups, devices, and service principals (T1213.003)&lt;/li&gt;
&lt;li&gt;Conduct AD reconnaissance on on-premises systems using:
&lt;ul&gt;
&lt;li&gt;Malware such as Atomic, Vidar, UltraKnot&lt;/li&gt;
&lt;li&gt;Legitimate software such as ADRecon, ADExplorer, PINGCASTLE&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Move Laterally&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Delegated admin access misuse across tenants or services&lt;/li&gt;
&lt;li&gt;Use of legitimate remote management and administrative tools such as RDP, SSH, VMware vCenter, Socat Linux Utility&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Complete Mission&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Data exfiltration from cloud storage, email, and SaaS platforms&lt;/li&gt;
&lt;li&gt;Ransomware deployment&lt;/li&gt;
&lt;li&gt;Rapid cleanup or abandonment once objectives are met&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Mapping to MITRE ATT&amp;amp;CK&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technique ID&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;T1598.004&lt;/td&gt;
&lt;td&gt;Phishing for Information: Spearphishing Voice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1656&lt;/td&gt;
&lt;td&gt;Impersonation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1219&lt;/td&gt;
&lt;td&gt;Remote Access Tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1578.002&lt;/td&gt;
&lt;td&gt;Modify Cloud Compute Infrastructure: Create Cloud Instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1213.003&lt;/td&gt;
&lt;td&gt;Data from Information Repositories: Code Repositories&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Domains Used by Scattered Spider&lt;/h2&gt;
&lt;p&gt;Scattered Spider uses look-alike domains such as &lt;code&gt;targetsname-sso[.]com&lt;/code&gt; or &lt;code&gt;targetsname-helpdesk[.]com&lt;/code&gt; to exploit employee trust in familiar identity and IT workflows. These domains are designed to appear legitimate to both users and helpdesk staff, increasing the success rate of credential harvesting, MFA fatigue, and social-engineering calls.&lt;/p&gt;
&lt;p&gt;By mimicking SSO, Okta, or service desk infrastructure, attackers can collect valid credentials and session material without deploying malware. This tactic also helps their activity blend into normal identity traffic, delaying detection by traditional security controls.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Example Domains&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;targetsname-sso[.]com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;targetsname-servicedesk[.]com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;targetsname-okta[.]com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;targetsname-cms[.]com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;targetsname-helpdesk[.]com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;oktalogin-targetcompany[.]com&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Real-World Scattered Spider Campaigns&lt;/h2&gt;
&lt;h3&gt;Caesars Entertainment and MGM Resort Attack&lt;/h3&gt;
&lt;p&gt;The September 2023 cyber incidents affecting MGM Resorts International and Caesars Entertainment serve as a cautionary example of how identity-centric attacks can disrupt large enterprises without exploiting software vulnerabilities.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/scattered-spider-tradecraft/infographic-2.png&quot; alt=&quot;Caesars &amp;amp; MGM - Scattered Spider Attack Timeline&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Attack Timeline&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sep 1, 2023&lt;/td&gt;
&lt;td&gt;Early reconnaissance suspicious activity observed in both organizations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 7, 2023&lt;/td&gt;
&lt;td&gt;Caesars breach confirmed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 10-11, 2023&lt;/td&gt;
&lt;td&gt;MGM large-scale outage; ransomware deployed across MGM infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 14, 2023&lt;/td&gt;
&lt;td&gt;Scattered Spider claims massive data theft from both casino giants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 18, 2023&lt;/td&gt;
&lt;td&gt;Divergent responses: Caesars pays ~$15M ransom, MGM refuses payment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Late Sep 2023&lt;/td&gt;
&lt;td&gt;Operations restored&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;Attack Lifecycle&lt;/h4&gt;
&lt;p&gt;In both incidents, the attack followed a similar identity-driven lifecycle. Initial access was achieved through social engineering, with attackers impersonating employees to manipulate IT support workflows. This enabled access to identity infrastructure, including Okta, which played a central role in authentication and access management.&lt;/p&gt;
&lt;p&gt;Once authenticated as legitimate users, the attackers leveraged weak or inconsistently enforced MFA controls to escalate privileges and expand access across cloud and SaaS environments. Rather than relying on malware exploits, the attackers operated through trusted identity sessions, allowing them to move laterally, access sensitive systems, and exfiltrate data while blending into normal administrative activity.&lt;/p&gt;
&lt;p&gt;In MGM&apos;s case, this access ultimately enabled deployment of &lt;strong&gt;BlackCat&lt;/strong&gt; (also known as &lt;strong&gt;ALPHV&lt;/strong&gt;) ransomware-as-a-service (RaaS) operation, resulting in widespread operational disruption.&lt;/p&gt;
&lt;h4&gt;Contributing Security Gaps&lt;/h4&gt;
&lt;p&gt;Several defensive shortcomings amplified the impact of both incidents:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inadequate MFA enforcement&lt;/strong&gt;, allowing attackers to bypass or manipulate authentication controls&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Insufficient employee security awareness&lt;/strong&gt;, increasing susceptibility to social engineering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Over-privileged identity access&lt;/strong&gt;, enabling rapid privilege escalation once inside&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited identity-focused detection and response&lt;/strong&gt;, delaying recognition of abnormal access patterns&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;CVE-2015-2291&lt;/h3&gt;
&lt;p&gt;CVE-2015-2291 is a local privilege escalation vulnerability in Intel Ethernet diagnostics drivers that allows a low-privileged local user to achieve kernel-level code execution through crafted IOCTL calls. While technically severe and listed in CISA&apos;s Known Exploited Vulnerabilities Catalog, this vulnerability represents a traditional endpoint exploitation path, not the primary intrusion vector observed in Scattered Spider&apos;s most impactful campaigns.&lt;/p&gt;
&lt;p&gt;In contrast to the Caesars and MGM incidents, where attackers relied almost entirely on social engineering and identity abuse, CVE-2015-2291 requires prior local access and a vulnerable driver to be present. As such, it is better understood as a post-compromise enabler rather than an initial access technique, potentially used to harden persistence or escalate control on already-compromised systems.&lt;/p&gt;
&lt;h2&gt;Defending Against Scattered Spider TTPs&lt;/h2&gt;
&lt;p&gt;Scattered Spider demonstrates a fundamental shift in how modern attacks succeed. Rather than exploiting software vulnerabilities or deploying large malware payloads, the group abuses identity systems, trusted workflows, and legitimate access paths to move through environments unnoticed.&lt;/p&gt;
&lt;h3&gt;Closing Doors&lt;/h3&gt;
&lt;p&gt;Scattered Spider exploits trust in identity workflows, not software vulnerabilities. The most effective hardening measures focus on closing identity gaps at the points most frequently abused.&lt;/p&gt;
&lt;h4&gt;Lock Down Helpdesk Identity Changes&lt;/h4&gt;
&lt;p&gt;Require positive identity verification and out-of-band confirmation for high-risk changes such as password resets, MFA changes, and account recovery. Avoid reliance on static personal data.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Helpdesk Identity Change Request
→ Require real-time TOTP verification generated by the user
→ Validate TOTP before proceeding with:
  - Password resets
  - MFA registration or removal
  - Recovery info changes
→ Block request if TOTP cannot be verified
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Enforce Phishing-Resistant Authentication&lt;/h4&gt;
&lt;p&gt;Remove SMS and voice-based MFA. Require strong, phishing-resistant MFA for all users, and stricter enforcement for privileged identities.&lt;/p&gt;
&lt;h4&gt;Restrict MFA Registration and Modification&lt;/h4&gt;
&lt;p&gt;Limit MFA changes to trusted locations and compliant devices. Investigate shared MFA devices or phone numbers across multiple accounts. For organizations that leverage Microsoft Entra ID, this can be accomplished using a Conditional Access Policy.&lt;/p&gt;
&lt;h4&gt;Reduce Standing Privilege&lt;/h4&gt;
&lt;p&gt;Enforce least-privilege access, separate admin and user identities, and use just-in-time access for privileged actions.&lt;/p&gt;
&lt;h4&gt;Bind Identity to Device Trust&lt;/h4&gt;
&lt;p&gt;Require device compliance and active endpoint protection as part of authentication. Monitor for new or unmanaged devices gaining access.&lt;/p&gt;
&lt;h4&gt;Minimize Lateral Movement Paths&lt;/h4&gt;
&lt;p&gt;Restrict remote authentication for local and service accounts and limit administrative access to hardened environments only.&lt;/p&gt;
&lt;p&gt;Closing these doors significantly reduces the attacker&apos;s ability to progress through the identity attack flow, shrinking the window for compromise before detection and response.&lt;/p&gt;
&lt;h2&gt;How SlashID Helps&lt;/h2&gt;
&lt;p&gt;This section outlines how an identity-first defense model disrupts Scattered Spider&apos;s attack flow, and how SlashID detects and stops these attacks before they escalate into full-scale breaches.&lt;/p&gt;
&lt;h3&gt;Identity Visibility and Governance&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Attackers exploit helpdesk workflows, MFA resets, OAuth grants, and dormant access&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How SlashID helps:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unified identity graph across IdP, SaaS, cloud, and on-prem&lt;/li&gt;
&lt;li&gt;Shadow SaaS and GenAI access visibility&lt;/li&gt;
&lt;li&gt;Vishing prevention with TOTP verification&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Authentication Abuse and MFA Manipulation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MFA fatigue, MFA re-registration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How SlashID helps:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Behavioral detection for MFA changes and authentication method drift&lt;/li&gt;
&lt;li&gt;One-click or automated remediation (enforce MFA, suspend users, rotate credentials)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Privilege Escalation, Persistence and Lateral Movement&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Over-privileged roles, OAuth abuse&lt;/li&gt;
&lt;li&gt;SSO-based lateral movement across SaaS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How SlashID helps:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Detection of permission drift and risky OAuth 2.0 grants&lt;/li&gt;
&lt;li&gt;Identification of newly privileged users and service accounts&lt;/li&gt;
&lt;li&gt;Behavioral anomaly detection across the identity graph&lt;/li&gt;
&lt;li&gt;Shadow SaaS discovery to identify unsanctioned access expansion&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Detection, Response, and Operational Speed&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Attacks succeed because detection is delayed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How SlashID helps:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;500+ built-in detections across your identity graph—no custom rules needed&lt;/li&gt;
&lt;li&gt;Native SIEM/SOAR integrations&lt;/li&gt;
&lt;li&gt;Connected view of users, non-human identities, and AI agents across environments&lt;/li&gt;
&lt;li&gt;Compliance-ready reporting (SOC 2, ISO 27001, PCI-DSS, and more)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Scattered Spider represents a new era of identity-centric attacks where adversaries don&apos;t need to exploit vulnerabilities—they simply log in using stolen or manipulated credentials. Defending against these threats requires a shift from traditional perimeter security to identity-first security models that provide visibility, detection, and rapid response across the entire identity attack surface.&lt;/p&gt;
&lt;p&gt;If you&apos;re looking to strengthen your defenses against identity-based attacks, &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;contact SlashID&lt;/a&gt; to learn how our platform can help protect your organization.&lt;/p&gt;
</content:encoded><author>SlashID Team, Vincenzo Iozzo</author></item><item><title>Authenticate your Shopify customers with SlashID</title><link>https://slashid.dev/blog/shopify-login-app/</link><guid isPermaLink="true">https://slashid.dev/blog/shopify-login-app/</guid><description>The new SlashID Login app for Shopify lets your customers authenticate seamlessly using quick and safe methods like passkeys, social login and magic links.</description><pubDate>Tue, 25 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&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=&quot;https://www.cio.com/article/234915/ditching-passwords-and-increasing-ecommerce-conversion-rates-by-54-2.html&quot;&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;img src=&quot;/blog/shopify-login-app/configuration.png&quot; alt=&quot;Configuration&quot; /&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=&quot;https://www.slashid.dev/&quot;&gt;website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Install the &lt;a href=&quot;https://apps.shopify.com/slashid-login&quot;&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;img src=&quot;/blog/shopify-login-app/customization.png&quot; alt=&quot;Customization&quot; /&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=&quot;https://shopify.dev/docs/api/multipass&quot;&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=&quot;https://developer.slashid.dev/docs/access/guides/webhooks&quot;&gt;Webhook&lt;/a&gt; and &lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/suborgs&quot;&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=&quot;https://console.slashid.dev/signup?utm_source=shopify-bp&quot;&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=&quot;mailto:contact@slashid.dev&quot;&gt;Let us know&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Ivan Kovic, Vincenzo Iozzo</author></item><item><title>Social logins in 5 minutes or less</title><link>https://slashid.dev/blog/social-login-5min/</link><guid isPermaLink="true">https://slashid.dev/blog/social-login-5min/</guid><description>Today we are releasing our OpenID Connect (OIDC) SSO module which you can use to add Social logins and OIDC-compatible SSO to your app in less than 5 minutes. Social logins can significantly boost user registration - for instance, Pinterest reported a 47% registration increase after adding Google One Tap to their website.</description><pubDate>Tue, 01 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Single Sign-On (SSO) is a popular choice amongst both services and users, increasing the service’s confidence in the user’s identity, and reducing the number of login credentials that users have to manage. You can now integrate SSO from third-party identity providers (IdPs) into your frontend with just a few lines of code using the /id SDK, and immediately start using additional features from /id.&lt;/p&gt;
&lt;p&gt;Social logins can significantly boost user registration, for instance Pinterest &lt;a href=&quot;https://developers.google.com/identity/casestudies/pinterest-casestudy.pdf&quot;&gt;reported&lt;/a&gt; a 47% registration increase after adding Google One Tap to their website.&lt;/p&gt;
&lt;h2&gt;A primer on SSO&lt;/h2&gt;
&lt;p&gt;What is SSO and how does it work?
SSO involves a user granting your service access to their identity information held by a third party IdP, such as Google, Apple, or Facebook. Your service can then retrieve this information and use it to build a user profile, which can be enriched with internal data you collect. This means you can immediately personalize your UX to the user based on the existing information, and you can have greater confidence in the user’s identity. From the user perspective, they use their existing credentials (e.g., Google account), and can continue to log in to your service with those same credentials, reducing friction and minimizing the number of accounts that they must track.&lt;/p&gt;
&lt;p&gt;There are multiple standards for implementing SSO. /id supports &lt;a href=&quot;https://openid.net/connect/&quot;&gt;OpenID Connect (OIDC)&lt;/a&gt;, a standard for authentication built on top of &lt;a href=&quot;https://oauth.net/2/&quot;&gt;OAuth 2.0&lt;/a&gt;. OIDC is supported by many popular IdPs. In brief, OIDC works as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You register your app with the IdP and obtain a client ID and secret&lt;/li&gt;
&lt;li&gt;When a user wants to use SSO, you call an API exposed by the IdP with your client ID&lt;/li&gt;
&lt;li&gt;The user is prompted to log in with the IdP&lt;/li&gt;
&lt;li&gt;Your service receives an authorization code&lt;/li&gt;
&lt;li&gt;Your service exchanges the authorization code with the IdP for identity and access tokens, authenticated with the client secret&lt;/li&gt;
&lt;li&gt;The identity token is a JSON Web Token (JWT) that includes information about the user; the access token can be used subsequently to retrieve user information from the IdP, within the scopes that the user allowed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With /id, you simply need to provide us with the client ID and secret, and call a single method from our SDK; we take care of the rest and return the tokens.&lt;/p&gt;
&lt;h2&gt;SSO in 5 minutes or less&lt;/h2&gt;
&lt;p&gt;We will use Google as an example. First, you need to obtain a client ID and client secret from Google - our dedicated &lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/SSO/oauth_creds_google&quot;&gt;guide&lt;/a&gt; provides step-by-step instructions on how to do this. Once you have these OAuth2 credentials, enabling SSO with SlashID requires just two steps.&lt;/p&gt;
&lt;p&gt;The first is to register the OAuth2 credentials through the SlashID APIs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl --location --request POST &apos;https://api.sandbox.slashid.com/organizations/sso/oidc/provider-credentials&apos; \
--header &apos;SlashID-OrgID: &amp;lt;YOUR ORG ID&amp;gt;&apos; \
--header &apos;SlashID-API-Key: &amp;lt;YOUR API KEY&amp;gt;&apos; \
--header &apos;Content-Type: application/json&apos; \
--data-raw &apos;{
    &quot;client_id&quot;: &quot;&amp;lt;CLIENT ID&amp;gt;&quot;,
    &quot;client_secret&quot;: &quot;&amp;lt;CLIENT SECRET&amp;gt;&quot;,
    &quot;provider&quot;: &quot;google&quot;,
    &quot;label&quot;: &quot;A label for this set of credentials&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second is to invoke the &lt;code&gt;.id()&lt;/code&gt; method from your frontend as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// In a module script
import * as slashid from &apos;@slashid/slashid&apos;

const org_id = &apos;my_org_id&apos; // organization ID, provided on registration with /id
const clientId = &apos;my.client.id&apos; // client ID from the IdP
const provider = &apos;google&apos; // the identity provider to authenticate with

const sid = new slashid.SlashID({ oid: org_id })

let user = await sid.id(org_id, null, {
  method: &apos;oidc&apos;,
  options: {
    client_id: clientId,
    provider: provider,
    ux_mode: &apos;popup&apos;,
  },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default SlashID will use a pop-up to prompt the user to login, but you can also decide to use a redirect flow instead. To do so, set &lt;code&gt;ux_mode&lt;/code&gt; to &lt;code&gt;”redirect”&lt;/code&gt; and add an event listener to your webpage like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;addEventListener(&apos;load&apos;, async (event) =&amp;gt; {
  let user = await sid.getUserFromURL()
  console.log(&apos;Retrieved user from URL&apos;, { user })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can optionally set &lt;code&gt;redirect_target&lt;/code&gt; to change the URL to redirect to after the flow is complete, by default it is the current page.&lt;/p&gt;
&lt;p&gt;Behind the scenes, the /id SDK and backend will carry out the OIDC flow with the IdP using the credentials matching the client ID provided. If you set UX mode to popup, the SDK will return a User object, which is a /id token. If set to redirect, the URL specified will be loaded, with an additional URL query parameter appended. You can then use the &lt;code&gt;getUserFromURL&lt;/code&gt; SDK method to obtain the User object.&lt;/p&gt;
&lt;p&gt;In both cases, the User object represents a signed JWT that contains both a /id user token and the OIDC tokens from the IdP.&lt;/p&gt;
&lt;p&gt;That’s it!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;SSO is a great tool to increase user registration as part of the onboarding process. While there’s a lot of complexity behind OIDC and OAuth 2.0, our aim at SlashID is to handle that complexity for you so you quickly experiment with new features and new onboarding flows.&lt;/p&gt;
&lt;p&gt;Please reach out to us if there’s a specific SSO provider you’d like us to support.&lt;/p&gt;
</content:encoded><author>Ivan Kovic, Vincenzo Iozzo</author></item><item><title>Single Sign-On implementation: Safely retrieving the email claim</title><link>https://slashid.dev/blog/sso-safe-email-claim/</link><guid isPermaLink="true">https://slashid.dev/blog/sso-safe-email-claim/</guid><description>A number of security issues have been discovered recently caused by the reliance on the email claim when using OpenID Connect (OIDC) for SSO.  In this blog post we&apos;ll review some of the major OIDC providers to discuss how to retrieve the claim safely</description><pubDate>Thu, 18 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;As discussed in a previous &lt;a href=&quot;../oauth-security&quot;&gt;blog post&lt;/a&gt; several high profile websites such as Booking.com, Kayak, and Grammarly have been affected by various issues relating to OpenID Connect (OIDC).&lt;/p&gt;
&lt;p&gt;One of the most common is the use of the &lt;code&gt;email&lt;/code&gt; claim as a stable identifier when authenticating a user.&lt;/p&gt;
&lt;p&gt;In this blog post we&apos;ll discuss why this happens and how to do it safely when implementing SSO.&lt;/p&gt;
&lt;h2&gt;The origin of the issue&lt;/h2&gt;
&lt;p&gt;The OIDC specification clarifies that the only authoritative claims a resource server such as Google can provide are the &lt;code&gt;sub&lt;/code&gt; (Subject) and &lt;code&gt;iss&lt;/code&gt; (Issuer) claims. In particular, the spec states that the &lt;code&gt;sub&lt;/code&gt; must be locally unique and never reassigned within the OIDC Server/Issuer to a different user.&lt;/p&gt;
&lt;p&gt;However, most issuers also include an &lt;code&gt;email&lt;/code&gt; claim in their ID tokens and many relying parties (RPs) use the &lt;code&gt;email&lt;/code&gt; address as a valid, unique claim. This happens for two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Most companies want to associate an email address with a user, so taking advantage of the &lt;code&gt;email&lt;/code&gt; claim decreases friction during sign-up&lt;/li&gt;
&lt;li&gt;As for many complex protocols, very few developers read the full specification before implementing it&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In this scenario what normally happens is that an account is created (or a user is authenticated into an account) in the RP server based on the &lt;code&gt;email&lt;/code&gt; claim.&lt;/p&gt;
&lt;h2&gt;The cost of getting it wrong&lt;/h2&gt;
&lt;p&gt;The ultimate issue here is the ability for an attacker to take over a victim&apos;s account. Several examples of this have been shown in the wild, in the last year:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Flickr using &lt;a href=&quot;https://security.lauritz-holtmann.de/advisories/flickr-account-takeover/&quot;&gt;AWS Cognito&lt;/a&gt;: In this case an attacker could overwrite the &lt;code&gt;email&lt;/code&gt; attributes on his account and impersonate another user since Flickr didn&apos;t check the &lt;code&gt;verified&lt;/code&gt; field.&lt;/li&gt;
&lt;li&gt;All apps using Sign-in with Microsoft until &lt;a href=&quot;https://msrc.microsoft.com/blog/2023/06/potential-risk-of-privilege-escalation-in-azure-ad-applications/&quot;&gt;June 2023&lt;/a&gt;: In this case an attacker could create a fake tenant with an unverified email, impersonating any user and ultimately taking over the account in the vulnerable application.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The status of the email claim by provider&lt;/h2&gt;
&lt;p&gt;Fortunately, a number of high profile identity providers (IdPs) provide ways to check the validity of an &lt;code&gt;email&lt;/code&gt; for a user. Let&apos;s see a few examples.&lt;/p&gt;
&lt;h3&gt;Google&lt;/h3&gt;
&lt;p&gt;Google exposes an &lt;a href=&quot;https://cloud.google.com/identity-platform/docs/reference/rest/v1/UserInfo&quot;&gt;API&lt;/a&gt; to fetch the email address. The API documentation says that the &lt;code&gt;email&lt;/code&gt; field in the response is the Google user&apos;s primary email address, which is always verified.&lt;/p&gt;
&lt;h3&gt;Microsoft&lt;/h3&gt;
&lt;p&gt;Microsoft has recently introduced a an optional claim &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity-platform/migrate-off-email-claim-authorization&quot;&gt;&lt;code&gt;xms_edov&lt;/code&gt;&lt;/a&gt; (Email Domain Owner Verified) in the ID Token that indicates whether an email claim contains a domain-verified email address.&lt;/p&gt;
&lt;p&gt;Further, and more safely, it is possible to use the &lt;code&gt;removeUnverifiedEmailClaim&lt;/code&gt; flag in the Graph &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity-platform/migrate-off-email-claim-authorization&quot;&gt;APIs&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Okta&lt;/h3&gt;
&lt;p&gt;Okta provides an &lt;a href=&quot;https://developer.okta.com/docs/reference/api/oidc/#userinfo&quot;&gt;API&lt;/a&gt; to retrieve the email of a logged-in user, and the response includes an &lt;code&gt;email_verified&lt;/code&gt; value.&lt;/p&gt;
&lt;h3&gt;Apple&lt;/h3&gt;
&lt;p&gt;Apple includes an &lt;code&gt;email_verified&lt;/code&gt; claim in the ID token returned via OIDC.&lt;/p&gt;
&lt;h3&gt;Github&lt;/h3&gt;
&lt;p&gt;You can retrieve the email through the Github &lt;a href=&quot;https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28&quot;&gt;API&lt;/a&gt; using the access token obtained via OIDC, and the response body contains the fields &lt;code&gt;primary&lt;/code&gt; and &lt;code&gt;verified&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Gitlab&lt;/h3&gt;
&lt;p&gt;You can retrieve the email through the Gitlab &lt;a href=&quot;https://docs.gitlab.com/ee/api/users.html&quot;&gt;API&lt;/a&gt; using the access token obtained via OIDC, but the response body does not contain an &lt;code&gt;email_verified&lt;/code&gt; field.&lt;/p&gt;
&lt;p&gt;Further, the Gitlab docs indicate that users may not always verify email addresses.&lt;/p&gt;
&lt;h3&gt;Bitbucket&lt;/h3&gt;
&lt;p&gt;You can retrieve the email through the Bitbucket API using the access token obtained via OIDC. The response body contains two fields: &lt;code&gt;is_primary&lt;/code&gt; and &lt;code&gt;is_confirmed&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, the Bitbucket &lt;a href=&quot;https://developer.atlassian.com/cloud/bitbucket/rest/api-group-users/#api-user-emails-email-get&quot;&gt;API&lt;/a&gt; does not contain any reference to these fields, so it&apos;s difficult to confirm exactly what these fields mean.&lt;/p&gt;
&lt;h3&gt;LINE&lt;/h3&gt;
&lt;p&gt;It is possible to retrieve the email by sending the ID token to the token verification &lt;a href=&quot;https://developers.line.biz/en/reference/line-login/#verify-id-token&quot;&gt;endpoint&lt;/a&gt;, which returns the email.
However there&apos;s no equivalent &lt;code&gt;email_verified&lt;/code&gt; claim, and the LINE docs do not explicitly say one way or the other if emails are verified.&lt;/p&gt;
&lt;h3&gt;Facebook&lt;/h3&gt;
&lt;p&gt;You can retrieve the email through the Facebook &lt;a href=&quot;https://developers.facebook.com/docs/graph-api/reference/user/&quot;&gt;API&lt;/a&gt; using the access token obtained via OIDC, if the user has an email address, which is not always the case with Facebook.&lt;/p&gt;
&lt;p&gt;The response doesn&apos;t contain an &lt;code&gt;email_verified&lt;/code&gt; field. The Facebook developer docs do not explicitly say if emails are verified, but the Facebook user-facing docs say that emails are verified at sign-up and when emails are added to a profile.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As the OIDC protocol specification states, ultimately it is unsafe to rely on the Issuer to verify the email of a user. If your application requires a high-level of assurance we recommend only relying on the &lt;code&gt;sub&lt;/code&gt; field and independently verifying the user&apos;s email address.&lt;/p&gt;
&lt;p&gt;If you are interested in implementing SSO securely, get a free account &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=oauth_security&quot;&gt;here&lt;/a&gt;
or reach out to &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;us&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Joseph Gardner, Vincenzo Iozzo</author></item><item><title>Protecting against Snowflake breaches</title><link>https://slashid.dev/blog/snowflake-breach-protection/</link><guid isPermaLink="true">https://slashid.dev/blog/snowflake-breach-protection/</guid><description>In the last few weeks several very high-profile breaches have been in the news, from Santander to Ticketmaster and AT&amp;T.  These breaches all have the same attack vector: identity-based attacks against Snowflake instances.  In this article, we discuss the causes of the breach and our approach to protect against identity-based attacks against Snowflake.</description><pubDate>Mon, 15 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In the last few weeks, numerous Snowflake customers experienced very significant data breaches.&lt;/p&gt;
&lt;p&gt;All the breaches were attributed to compromised credentials and the lack of multi-factor authentication (MFA) among customers.&lt;/p&gt;
&lt;p&gt;The attackers utilized stolen credentials from various infostealer malware to gain access to sensitive data. High-profile companies such as Ticketmaster, Santander, Advance Auto Parts, and, most recently, AT&amp;amp;T were affected. The attackers believed to be the &lt;a href=&quot;https://en.wikipedia.org/wiki/ShinyHunters&quot;&gt;ShinyHunters group&lt;/a&gt;, are one of the more prolific groups involved in several high-profile breaches over the years.&lt;/p&gt;
&lt;p&gt;Let&apos;s dig deeper into Snowflake and how to prevent these breaches in the future.&lt;/p&gt;
&lt;h2&gt;The Snowflake identity model&lt;/h2&gt;
&lt;p&gt;Snowflake has 3 key identity and access concepts that form the backbone of IAM.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Roles:&lt;/strong&gt; They define a set of permissions that determine what actions a user or a group of users can perform within the Snowflake environment. Roles can be organized in a hierarchical manner, where roles can inherit permissions from other roles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Users:&lt;/strong&gt; Users in Snowflake represent individuals or entities that need access to Snowflake&apos;s resources. Crucially, users can be used as &quot;service accounts&quot; through passwords and RSA key pairs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service Integration:&lt;/strong&gt; These represent integration with third-party systems, either via OAuth 2.0 or API keys. This is also how a Snowflake instance can be provisioned through a third-party IdP.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notably, Snowflake natively supports only the following authentication methods for a User:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Password&lt;/li&gt;
&lt;li&gt;RSA keys&lt;/li&gt;
&lt;li&gt;MFA via Duo&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As discussed, from what is publicly known, all the breaches in the past few months show the same pattern: the attacker ran a credential-stuffing attack against Snowflake instances that weren&apos;t protected by RSA.&lt;/p&gt;
&lt;p&gt;Note, however, that this is the low-hanging fruit. More sophisticated attackers could target MFA-enabled accounts through AITM, MFA Fatigue, and several other attacks.&lt;/p&gt;
&lt;h2&gt;How can SlashID help&lt;/h2&gt;
&lt;p&gt;As we discussed in our previous article on NHI - at SlashID we believe that the Identity Security maturity model should follow a familiar pattern we have seen in endpoints and other security areas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Visibility&lt;/li&gt;
&lt;li&gt;Detection&lt;/li&gt;
&lt;li&gt;Remediation&lt;/li&gt;
&lt;li&gt;Prevention&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Towards this end, we are happy to announce support for Snowflake in our Identity Security product.&lt;/p&gt;
&lt;p&gt;Through SlashID you can:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Visualize and collect all roles, users, and service integrations for all your Snowflake instances&lt;/li&gt;
&lt;li&gt;Detect identity-based attacks or lateral movement&lt;/li&gt;
&lt;li&gt;Prevent and remediate by rotating credentials and dropping privileges&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/blog/snowflake-breach-protection/snowflake-screenshot1.png&quot; alt=&quot;identity list&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In particular, SlashID can detect several attack patterns, including:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The provisioning of malicious users or integrations&lt;/li&gt;
&lt;li&gt;Credential stuffing attempts&lt;/li&gt;
&lt;li&gt;Authentication attempts from malicious or suspicious IP addresses, times of the data, and locations&lt;/li&gt;
&lt;li&gt;Overprivileged accounts&lt;/li&gt;
&lt;li&gt;Stale accounts and credentials&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/blog/snowflake-breach-protection/snowflake-screenshot2.png&quot; alt=&quot;user detail&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Beyond detections, SlashID can help rotate credentials, suspend/block users, turn on MFA, and, update roles.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Snowflake contains some of the most sensitive user data a company has and it&apos;s a very complicated system to secure properly, &lt;a href=&quot;https://www.slashid.com/contact/&quot;&gt;reach out to us&lt;/a&gt; to see how SlashID can help secure your Snowflake instances.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Access tokens strike again, the Salesloft Drift breach</title><link>https://slashid.dev/blog/salesloft-drift-breach/</link><guid isPermaLink="true">https://slashid.dev/blog/salesloft-drift-breach/</guid><description>In mid‑August 2025, GTIG confirmed a large‑scale credential‑harvesting and data‑theft campaign abusing trusted OAuth integrations (Drift) to access Salesforce orgs.  Attackers (UNC6395) used stolen Drift OAuth tokens to mass‑query Salesforce, mine embedded secrets (AWS, Snowflake), and pivot into connected platforms.  This post reconstructs the attack flow, maps it to MITRE ATT&amp;CK, and outlines immediate detection and defense actions.</description><pubDate>Sun, 21 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;In mid‑August 2025, the Google Threat Intelligence Group (GTIG) confirmed a large‑scale credential‑harvesting and data‑theft campaign targeting organizations through their Salesforce instances. Attackers systematically siphoned sensitive business data (Accounts, Cases, Users, Opportunities), actively searched for secrets like AWS keys and Snowflake tokens, and leveraged Tor exit nodes to obfuscate their operations.&lt;/p&gt;
&lt;p&gt;The campaign, attributed to &lt;strong&gt;UNC6395&lt;/strong&gt;, leveraged &lt;strong&gt;stolen OAuth tokens&lt;/strong&gt; from the trusted third‑party application &lt;strong&gt;Salesloft Drift&lt;/strong&gt;, pivoting across multiple integrations and exfiltrating sensitive corporate data. This supply‑chain attack didn’t target Salesforce directly.&lt;/p&gt;
&lt;p&gt;Instead, attackers compromised the &lt;strong&gt;Drift chatbot&lt;/strong&gt; and hijacked its authorized access to third-party victims by abusing &lt;strong&gt;long‑lived, overly permissive, and poorly scoped OAuth tokens&lt;/strong&gt;, exactly the scenario we warned about in our Illicit Consent‑Granting post.&lt;/p&gt;
&lt;p&gt;This breach is a textbook example of that scenario becoming reality. See: &lt;a href=&quot;https://www.slashid.dev/blog/entra-app-backdooring/#attack-lifecycle&quot;&gt;Illicit Consent‑Granting &amp;amp; App Backdooring – Obtaining persistence in Entra&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The breach unfolded in several layers:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Initial compromise of Drift OAuth tokens.&lt;/li&gt;
&lt;li&gt;Mass querying of Salesforce objects (Account, Case, User, Opportunity).&lt;/li&gt;
&lt;li&gt;Harvesting of embedded secrets (AWS keys, Snowflake tokens, VPN URLs).&lt;/li&gt;
&lt;li&gt;Lateral expansion into &lt;strong&gt;Drift Email → Google Workspace&lt;/strong&gt; accounts.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This post reconstructs the attack flow, maps the campaign against the MITRE ATT&amp;amp;CK framework, and outlines detection + defense measures enterprises must take immediately.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The incident highlights the danger of over-permissive SaaS integrations and the critical need to restrict OAuth access.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Why does this matter?&lt;/h2&gt;
&lt;p&gt;Salesforce is often the single source of truth for customer data: opportunities, revenue pipelines, support cases, and executive contact info. Compromise here is devastating. Worse, Salesforce often becomes a &lt;strong&gt;secret graveyard&lt;/strong&gt;: API keys pasted into case notes, passwords sent between reps, Snowflake tokens shared for quick data pulls.&lt;/p&gt;
&lt;p&gt;UNC6395 understood this perfectly. By querying Salesforce and systematically pulling records, the attackers weren’t just after names and emails — they were after the secret &lt;strong&gt;keys to the kingdom&lt;/strong&gt;, access tokens. And by leveraging OAuth, they bypassed the usual controls: no phishing, no MFA prompts, no password brute‑forcing, no anomalous login attempt detection. Just a token.&lt;/p&gt;
&lt;p&gt;An estimated &lt;strong&gt;700 companies&lt;/strong&gt; were affected, including major tech organizations such as &lt;strong&gt;Cloudflare&lt;/strong&gt;, &lt;strong&gt;Zscaler&lt;/strong&gt;, and &lt;strong&gt;Palo Alto Networks&lt;/strong&gt;. While the initial focus was Salesforce, GTIG later warned that &lt;strong&gt;other platforms connected via Drift&lt;/strong&gt;—including &lt;strong&gt;Google Workspace, Slack, AWS, Microsoft Azure, OpenAI, and Amazon S3&lt;/strong&gt;—might also be compromised.&lt;/p&gt;
&lt;p&gt;A few takeaways that stand out:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;To our knowledge, no exploits were used at any point.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Attackers leveraged valid tokens that typically don’t create new login events in audit logs, evading many auth/login‑related detections.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Much of the activity came from reputable cloud infrastructure, making IP‑reputation‑based detections significantly harder.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Drift isn’t unique here: many OAuth 2.0 apps are over‑permissioned because available scopes aren’t fine‑grained enough.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Overall, this breach underscores a newer, stealthier class of SaaS‑to‑SaaS intrusions—harder to spot than traditional endpoint/CSP incidents and covered by far fewer SOC detection capabilities.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;What is Drift, and what does it have access to&lt;/h2&gt;
&lt;p&gt;Drift is a conversational AI product that has extensive capabilities for Salesforce, including:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create and update Leads in SFDC that originate in Drift&lt;/li&gt;
&lt;li&gt;Create and update Contacts in SFDC that originate in Drift&lt;/li&gt;
&lt;li&gt;Send chat activity from Drift to SFDC&lt;/li&gt;
&lt;li&gt;Send meetings booked in Drift to SFDC&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As a result, Drift requests extensive permissions in Salesforce. In particular, a Drift integration can do the following:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Salesforce Object&lt;/th&gt;
&lt;th&gt;Object Permissions&lt;/th&gt;
&lt;th&gt;Field Permissions&lt;/th&gt;
&lt;th&gt;Field Name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Accounts&lt;/td&gt;
&lt;td&gt;Read, View All&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;Account Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accounts&lt;/td&gt;
&lt;td&gt;Read, View All&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accounts&lt;/td&gt;
&lt;td&gt;Read, View All&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;Website&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Campaigns&lt;/td&gt;
&lt;td&gt;Read, View All&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;Campaign Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cases&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Account Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cases&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Case Origin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cases&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Contact Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cases&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Description&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cases&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Subject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contacts&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Account Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contacts&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contacts&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Lead Source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contacts&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Mailing Address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contacts&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Events&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Assigned To&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Events&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;Created By&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Events&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;Last Modified By&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Events&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Events&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Related To&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Events&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leads&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leads&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leads&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Lead Source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leads&lt;/td&gt;
&lt;td&gt;Read, Create, Edit, View All&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opportunities&lt;/td&gt;
&lt;td&gt;Read, View All&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;Account Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opportunities&lt;/td&gt;
&lt;td&gt;Read, View All&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;Amount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opportunities&lt;/td&gt;
&lt;td&gt;Read, View All&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;Primary Campaign Source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opportunities&lt;/td&gt;
&lt;td&gt;Read, View All&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tasks&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Assigned To&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tasks&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Comments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tasks&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Due Date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tasks&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tasks&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Related To&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tasks&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Read, Edit&lt;/td&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;Given the extensive permissions, it is no wonder that Drift makes for a perfect target for an attacker&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Attack Flow&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/blog/salesforce-drift/attack-flow.png&quot; alt=&quot;Attack Flow Diagram&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;High‑level sequence:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Compromise trusted third‑party (Drift)&lt;/strong&gt; and harvest OAuth tokens.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authenticate to Salesforce&lt;/strong&gt; via valid tokens; enumerate and bulk‑query core objects.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mine embedded secrets&lt;/strong&gt; from exports; operationalize access to cloud/data platforms.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pivot via Drift Email&lt;/strong&gt; into scoped &lt;strong&gt;Google Workspace&lt;/strong&gt; accounts; expand blast radius.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This breach didn’t target Salesforce directly. Instead, attackers compromised a trusted third-party integration, Salesloft’s Drift chatbot, and hijacked its authorized access to downstream systems. This is a supply-chain style compromise where OAuth trust relationships become the weakest link.&lt;/p&gt;
&lt;p&gt;That’s exactly what makes this a devastating supply-chain attack: one vulnerable integration triggered a cascade across hundreds of organizations.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One vulnerable integration triggered a cascade across hundreds of organizations — a classic &lt;strong&gt;supply‑chain&lt;/strong&gt; failure of OAuth trust relationships.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;A similar supply‑chain heist (Bybit, Feb 2025)&lt;/h3&gt;
&lt;p&gt;A comparable attack in &lt;strong&gt;February 2025&lt;/strong&gt; led to one of the largest crypto heists: ~&lt;strong&gt;400,000 ETH&lt;/strong&gt; were stolen from &lt;strong&gt;Bybit&lt;/strong&gt; after attackers compromised its trusted provider &lt;strong&gt;Safe{Wallet}&lt;/strong&gt; by infiltrating a developer environment with stolen AWS session tokens. UI manipulation tricked signers into approving a malicious transaction that redirected funds to attacker wallets. The pattern: &lt;strong&gt;trusted third‑party → stolen tokens → authorized abuse&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Tools in the attacker’s toolbox&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stolen OAuth tokens&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standard Salesforce SOQL&lt;/strong&gt; queries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tor&lt;/strong&gt; for anonymity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated fetchers&lt;/strong&gt; (User‑Agents like &lt;code&gt;Salesforce-Multi-Org-Fetcher/1.0&lt;/code&gt;, &lt;code&gt;python-requests/2.32.4&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;MITRE ATT&amp;amp;CK Mapping&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;T1528&lt;/strong&gt; – Steal Application Access Token&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T1078&lt;/strong&gt; – Valid Accounts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T1530&lt;/strong&gt; – Data from Cloud Storage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T1552&lt;/strong&gt; – Unsecured Credentials&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Attack Lifecycle&lt;/h2&gt;
&lt;h3&gt;Phase 1: Initial Access&lt;/h3&gt;
&lt;p&gt;Attackers obtained valid OAuth tokens from Drift. From Salesforce’s perspective, these were &lt;strong&gt;trusted, legitimate sessions&lt;/strong&gt;—no exploits against Salesforce or Google required.&lt;/p&gt;
&lt;p&gt;While early hypotheses pointed to phishing/social engineering, subsequent findings indicate a different root cause.&lt;/p&gt;
&lt;p&gt;Mandiant’s investigation has since clarified that between March and June 2025, the threat actor accessed Salesloft’s GitHub account, where they downloaded repositories, added a guest user, and established workflows. This activity led to Drift’s AWS environment being accessed, which ultimately gave the attackers OAuth tokens for Drift customers’ integrations. So, this was how the Drift OAuth tokens were compromised.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; the breach didn’t begin with Salesforce or Drift OAuth tokens — it began months earlier in &lt;strong&gt;GitHub&lt;/strong&gt;, cascading into &lt;strong&gt;Drift OAuth abuse&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/blog/salesforce-drift/attack-timeline.png&quot; alt=&quot;Attack Timeline&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Phase 2: Salesforce Access &amp;amp; Data Harvesting&lt;/h3&gt;
&lt;p&gt;With tokens in hand, attackers started to perform recon in the environment. Based on the Cloudflare write-up on the incident, we know that the
attackers would perform the following actions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Send a GET request for a list of objects in the Salesforce tenant: &lt;code&gt;/services/data/v58.0/sobjects/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Send a GET request for metadata information for case objects in the Salesforce tenant: &lt;code&gt;/services/data/v58.0/sobjects/Case/describe/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A series of systematic SOQL queries:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;SELECT COUNT() FROM Account;
SELECT COUNT() FROM Opportunity;
SELECT COUNT() FROM User;
SELECT COUNT() FROM Case;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;They pulled full &lt;strong&gt;User&lt;/strong&gt; tables, complete with emails, names, phone numbers, and login timestamps:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;SELECT Id, Username, Email, FirstName, LastName, Name, Title, CompanyName, Department, Division, Phone, MobilePhone, IsActive,
       LastLoginDate, CreatedDate, LastModifiedDate, TimeZoneSidKey, LocaleSidKey, LanguageLocaleKey, EmailEncodingKey
FROM User
WHERE IsActive = true
ORDER BY LastLoginDate DESC NULLS LAST
LIMIT 20;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Phase 3: Secret Mining &amp;amp; Covering Tricks&lt;/h3&gt;
&lt;p&gt;After exfiltrating bulk Salesforce data, UNC6395 hunted for &lt;strong&gt;embedded credentials&lt;/strong&gt; and tokens to extend the compromise beyond Salesforce.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Likely techniques:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Regex scrapers&lt;/strong&gt; for known credential formats.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TruffleHog / Gitleaks‑style&lt;/strong&gt; scans for entropy‑heavy strings, AWS patterns, API key regexes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Common patterns:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AKIA[0-9A-Z]{16}                  # AWS Access Key
snowflakecomputing\.com           # Snowflake domain references
(password|secret|key)\s*[:=]      # Generic key-value credential markers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once stolen, these secrets enable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloud Compromise&lt;/strong&gt; – AWS keys let attackers spin up EC2 instances, exfiltrate S3 buckets, or pivot into the broader cloud estate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Warehouse Breaches&lt;/strong&gt; – Snowflake tokens allow queries into sensitive analytics data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Critically, attackers routinely weaponize cloud infrastructure itself once they gain valid access. For example, IP address &lt;code&gt;44.215.108.109&lt;/code&gt;, traced back to Amazon AWS (AS14618), but was flagged only by two vendors as “Malicious” or “Suspicious,”.&lt;/p&gt;
&lt;p&gt;This show how attackers routinely expand their real estate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS keys or cloud accounts can be abused to spawn disposable EC2 instances.&lt;/li&gt;
&lt;li&gt;Those instances then act as staging servers for exfiltration or further attacks.&lt;/li&gt;
&lt;li&gt;Because the traffic originates from “legitimate” AWS IPs, it often evades enterprise IP reputation blocks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This example highlights the downstream impact of the Salesforce – Drift compromise: attackers weren’t just stealing data; they were turning secrets into infrastructure to sustain operations.&lt;/p&gt;
&lt;h3&gt;Phase 4: Expansion &amp;amp; Fallout — Drift Email → Google Workspace&lt;/h3&gt;
&lt;p&gt;UNC6395 didn’t stop at Salesforce. They realized that OAuth trust relationships through Drift could extend into other ecosystems — and the Drift Email integration was the next pivot.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Aug 9, 2025:&lt;/strong&gt; Using compromised Drift OAuth tokens tied to &lt;strong&gt;Drift Email&lt;/strong&gt;, attackers gained &lt;strong&gt;scoped&lt;/strong&gt; access to a subset of &lt;strong&gt;Google Workspace&lt;/strong&gt; accounts integrated with Drift (not domain‑wide; only explicitly connected accounts).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aug 28, 2025:&lt;/strong&gt; Google &lt;strong&gt;revoked affected OAuth tokens&lt;/strong&gt; and &lt;strong&gt;disabled the Drift Email integration&lt;/strong&gt;, cutting off this pivot.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;(Bonus) Covering Tracks: Deleting Jobs... but Leaving Footprints&lt;/h3&gt;
&lt;p&gt;Attackers deleted query jobs after exfiltration to make it harder for incident responders to quickly see “large export jobs” or unusual query activity. To a casual admin, it would appear as if nothing abnormal was scheduled.&lt;/p&gt;
&lt;p&gt;Even though the job objects were removed, the underlying event logs persisted. Salesforce logs record the fact that queries were run, including timestamps, User-Agent strings, and IP addresses. So while first-response visibility was degraded, forensic teams with log access could still reconstruct attacker activity.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/salesforce-drift/cover-tracks.png&quot; alt=&quot;Cover tracks&quot; /&gt;&lt;/p&gt;
&lt;p&gt;What This Tells Us About UNC6395?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;They’re operationally mature: not smash-and-grab amateurs.&lt;/li&gt;
&lt;li&gt;They assumed many orgs don’t actively parse or monitor Salesforce logs in real time.&lt;/li&gt;
&lt;li&gt;Their tactic was “slow down the defenders, not stop them entirely.”&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;What this tells us about UNC6395&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Operationally mature&lt;/strong&gt;—far from smash‑and‑grab.&lt;/li&gt;
&lt;li&gt;They assume many orgs &lt;strong&gt;don’t parse Salesforce logs in real time&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Their tactic: &lt;strong&gt;slow down&lt;/strong&gt; defenders rather than stop visibility entirely.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Locking the doors — lesson learned&lt;/h2&gt;
&lt;p&gt;Unlike passwords, &lt;strong&gt;tokens are invisible to users&lt;/strong&gt; and &lt;strong&gt;bypass MFA&lt;/strong&gt;. Without strict monitoring and short lifespans, they become a silent backdoor into cloud systems.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Response Actions&lt;/h2&gt;
&lt;h3&gt;Immediate Response (Google)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Revoke all Drift OAuth tokens&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rotate&lt;/strong&gt; all Salesforce API keys, &lt;strong&gt;AWS keys&lt;/strong&gt;, &lt;strong&gt;Snowflake tokens&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disable Drift&lt;/strong&gt; integration&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Response (Salesloft + Mandiant)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Took &lt;strong&gt;Drift offline&lt;/strong&gt; and &lt;strong&gt;rotated credentials&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardened&lt;/strong&gt; the Salesloft environment against observed TTPs.&lt;/li&gt;
&lt;li&gt;Verified &lt;strong&gt;Drift–Salesloft segmentation&lt;/strong&gt; to ensure no lateral compromise.&lt;/li&gt;
&lt;li&gt;Conducted &lt;strong&gt;forensic QA&lt;/strong&gt; to confirm containment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Long‑Term Hardening&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reduce session lifespans&lt;/strong&gt; for OAuth tokens.&lt;/li&gt;
&lt;li&gt;Treat &lt;strong&gt;OAuth tokens like privileged accounts&lt;/strong&gt;; monitor and protect them accordingly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rotate refresh tokens&lt;/strong&gt; regularly; prefer short‑lived tokens with tight scopes.&lt;/li&gt;
&lt;li&gt;Review all third-party integrations and their permissions&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Detection in Practice: Spotting Unusual SOQL Queries&lt;/h2&gt;
&lt;p&gt;Attackers relied on &lt;strong&gt;mass SOQL&lt;/strong&gt; to exfiltrate at scale. Normal integrations fetch dozens/hundreds of records—not tens of thousands.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT EventType, CreatedDate, UserId, RowsProcessed, RowsReturned, ClientIp, USER_AGENT
FROM EventLogFile
WHERE EventType = &apos;API&apos;
AND RowsReturned &amp;gt; 10000
AND CreatedDate = LAST_N_DAYS:7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Defender actions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Monitor &lt;strong&gt;Salesforce EventLogFile&lt;/strong&gt; for &lt;strong&gt;abnormal query volumes&lt;/strong&gt;, unusually broad &lt;code&gt;SELECT&lt;/code&gt; queries, and high‑rate API access.&lt;/li&gt;
&lt;li&gt;Alert on &lt;strong&gt;atypical User‑Agents&lt;/strong&gt; and authentication from &lt;strong&gt;Tor/AWS IP ranges&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Correlate &lt;strong&gt;spikes&lt;/strong&gt; in queries with &lt;strong&gt;token issuance/refresh&lt;/strong&gt; events from Drift.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Indicators of Compromise (IOCs)&lt;/h2&gt;
&lt;h3&gt;Malicious / Suspicious User‑Agents&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Salesforce-Multi-Org-Fetcher/1.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Salesforce-CLI/1.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;python-requests/2.32.4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Python/3.11 aiohttp/3.12.15&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Observed IPs&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;44.215.108.109&lt;/strong&gt; (AWS)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;208.68.36.90&lt;/strong&gt; (DigitalOcean)&lt;/li&gt;
&lt;li&gt;Multiple &lt;strong&gt;Tor&lt;/strong&gt; exit nodes (e.g., &lt;code&gt;185.220.x.x&lt;/code&gt;, &lt;code&gt;192.42.x.x&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;OAuth trust relationships can become the &lt;strong&gt;weakest link&lt;/strong&gt; in cloud ecosystems. UNC6395 exploited Drift’s access to &lt;strong&gt;quietly drain Salesforce data&lt;/strong&gt; and &lt;strong&gt;weaponize embedded secrets&lt;/strong&gt;, then &lt;strong&gt;pivot&lt;/strong&gt; into connected platforms.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do now:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Revoke/rotate tokens and keys; &lt;strong&gt;disable&lt;/strong&gt; unnecessary integrations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shorten&lt;/strong&gt; token lifetimes; &lt;strong&gt;tighten scopes&lt;/strong&gt;; treat tokens as high‑privilege credentials.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuously monitor&lt;/strong&gt; Salesforce logs for bulk queries, unusual User‑Agents, and suspicious IPs.&lt;/li&gt;
&lt;li&gt;Review all third-party integrations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Identity is the new perimeter.&lt;/strong&gt;&lt;/p&gt;
</content:encoded><author>SlashID Team, Vincenzo Iozzo</author></item><item><title>The good, the bad and the ugly of Apple Passkeys</title><link>https://slashid.dev/blog/passkeys-deepdive/</link><guid isPermaLink="true">https://slashid.dev/blog/passkeys-deepdive/</guid><description>The widely anticipated Apple passkeys launch happened just a few weeks ago with the iOS 16 release. Passkeys are a cross-device extension of FIDO credentials compatible with WebAuthn. They address the main UX issue of WebAuthn, cross-device credentials. In this article we’ll explore the Apple passkeys implementation, how passkeys compare to traditional FIDO credentials and why the decision of Apple to get rid of device attestation and resident keys is a significant step back for security.</description><pubDate>Fri, 23 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Read up on Passkeys adoption trends in Top 50 &lt;a href=&quot;https://www.slashid.dev/blog/passkeys-adoption/&quot;&gt;websites&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Or take a look at an example app using SlashID to implement Passkeys and WebAuthn on &lt;a href=&quot;https://github.com/slashid/website-passkeys-playground&quot;&gt;GitHub&lt;/a&gt; or a &lt;a href=&quot;https://passkeys-playground.slashid.dev/&quot;&gt;live demo&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;How do passkeys work?&lt;/h2&gt;
&lt;p&gt;The Fast IDentity Online (FIDO) alliance together with a number of tech companies and the World Wide Web Consortium (W3C) have been developing ways to add public-key cryptography to browsers for several years. From that effort, the WebAuthn standard was born.&lt;/p&gt;
&lt;p&gt;As we discussed extensively in this blog, WebAuthn allows users to authenticate to a remote server by proving ownership of a private key stored in a &lt;em&gt;Roaming Authenticator&lt;/em&gt; (e.g., a Yubikey, a Titan key) or a &lt;em&gt;Platform Authenticator&lt;/em&gt; (e.g., the built-in keychain on Apple devices).
Multi-device FIDO credentials, also known as passkeys, extend the Platform Authenticator concept to allow the import/export of private keys from one device to another.&lt;/p&gt;
&lt;p&gt;How that operation is performed is highly dependent on the vendor; in this post we&apos;ll focus on Apple.&lt;/p&gt;
&lt;h2&gt;Passkeys According To Apple&lt;/h2&gt;
&lt;h3&gt;The good&lt;/h3&gt;
&lt;p&gt;The obvious advantage of passkeys is the improved user experience.&lt;/p&gt;
&lt;p&gt;Besides UX, passkeys also preserve the anti-phishing properties of WebAuthn which from a secuirty standpoint is the most important feature of the standard.&lt;/p&gt;
&lt;p&gt;The WebAuthn standard is by far the most credible attempt to make passwords obsolete and passkeys are a key step in that direction. In fact, the main obstacle to widespread WebAuthn adoption has been the issue of porting credentials from one device to another and passkeys are a solution to that.&lt;/p&gt;
&lt;h3&gt;The bad&lt;/h3&gt;
&lt;p&gt;The primary disadvantage of passkeys is that their security profile is significantly weaker than a traditional, hardware-bound, &lt;em&gt;Platform Authenticator&lt;/em&gt;- or &lt;em&gt;Roaming Authenticator&lt;/em&gt;-generated keypair.&lt;/p&gt;
&lt;p&gt;If we take an iOS device as an example, before passkeys, a platform authenticator key would normally be stored in Apple’s Secure Enclave. The Secure Enclave is a dedicated secure subsystem that is isolated from the main processor and is designed to keep sensitive data secure even when the kernel becomes compromised.
When the platform authenticator key is stored in the Secure Enclave, two things happen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The private key is tied to that device and cannot be exported or recovered if the device is lost&lt;/li&gt;
&lt;li&gt;Access to the private key is gated by biometric verification, such as TouchID or FaceID.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two security safeguards are lost with passkeys, because, despite being stored in the Secure Enclave, the private key is exported to iCloud and hence its strength is only as good as the iCloud recovery process.&lt;/p&gt;
&lt;h3&gt;The ugly&lt;/h3&gt;
&lt;p&gt;iOS 16 makes it impossible to use device-bound WebAuthn keys, effectively downgrading WebAuthn security on Apple devices to an AppleID reset flow.&lt;/p&gt;
&lt;p&gt;Further, it is not possible to perform &lt;a href=&quot;https://developer.apple.com/forums/thread/708982&quot;&gt;attestation&lt;/a&gt; of the authenticator. This, among other things, makes device verification harder and prevents novel applications of WebAuthn such as getting rid of &lt;a href=&quot;https://blog.cloudflare.com/introducing-cryptographic-attestation-of-personhood/&quot;&gt;CAPTCHAs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Apple’s stance on the topic seems to &lt;a href=&quot;https://developer.apple.com/forums/thread/708982&quot;&gt;be&lt;/a&gt; that since the key can be moved to other devices, it doesn’t make sense to provide an AAGUID or an attestation statement.&lt;/p&gt;
&lt;p&gt;While the statement is technically accurate, the lack of an attestation statement means that you can&apos;t prove the key has been stored, or even generated, safely because you can&apos;t infer properties/provenance of the authenticator.&lt;/p&gt;
&lt;h2&gt;The technical details&lt;/h2&gt;
&lt;h3&gt;What do passkeys look like compared to traditional Platform Authenticator keys?&lt;/h3&gt;
&lt;p&gt;Let’s examine what happens when a new credential is created through &lt;code&gt;navigator.credentials.create()&lt;/code&gt; and the browser returns a &lt;code&gt;PublicKeyCredential&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;This object contains one important structure: the &lt;code&gt;attestationObject&lt;/code&gt;.
The &lt;code&gt;attestationObject&lt;/code&gt; is a CBOR-encoded blob containing data about the attestation. This data is what allows a relying party to verify the chain of trust of the authenticator. Whether this data is present or not depends on whether the &lt;code&gt;navigator.credentials.create()&lt;/code&gt; function was called with an &lt;code&gt;attestation&lt;/code&gt; parameter.&lt;/p&gt;
&lt;p&gt;To compare Apple changes versus the standard, let’s first look at Chrome.&lt;/p&gt;
&lt;p&gt;This is what an &lt;code&gt;attestationObject&lt;/code&gt; looks like for a key generated via Chrome:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;b&apos;{&quot;type&quot;:&quot;webauthn.create&quot;,&quot;challenge&quot;:&quot;mKuoQhqah7SLIxbinNzgu1jgzRGa0V0i_0Bw3WnSi3U&quot;,&quot;origin&quot;:&quot;http://localhost:5000&quot;,&quot;crossOrigin&quot;:false,&quot;other_keys_can_be_added_here&quot;:&quot;do not compare clientDataJSON against a template. See https://goo.gl/yabPex&quot;}&apos;
format: packed
attStmt: {   &apos;alg&apos;: -7,
  &apos;sig&apos;: b&apos;0F\x02!\x00\xed6\xea\x12\xbao\x80\xf8\xd9Z\xae@\x1bu\xb0`\xb2O\x81&apos;
         b&apos;\x94H\x0bcv\xd9\xe3\x8c\x8cKR\x1a\xbc\x02!\x00\xc2\xa4\xbd!&apos;
         b&apos;\xbc=\xab\xa0\x1c\xac\xfd`\xeb\xf4\xcb\n\xc2}\x84\xb8\x1d$oE&apos;
         b&apos;\xf3\xc3\t\xd4\xffJu\xd3&apos;}
{   &apos;attestedCredData&apos;: {
              &apos;aaguid&apos;: b&apos;\xad\xce\x00\x025\xbc\xc6\nd\x8b\x0b%&apos;
              b&apos;\xf1\xf0U\x03&apos;,
              &apos;credentialId&apos;: b&apos;\x8c\xdf\x99\xb6b\x13I\xd1&apos;
                              b&apos;\x9e\x83\x04lT\x04\x84=_)|\x96&apos;
                              b&apos;I\xf5\xb5\\8r\xeb\xb8\x9f=\xc6N&apos;
                              b&apos;\xa7Lz-\xb4\xe7\xca\x8c&apos;
                              b&apos;\xaa\xc7\xa5{]\x06i\x9f&apos;
                              b&apos;{\xb6\xe1\x9d\xf0\xa0]\t&apos;
                              b&apos;&amp;gt;G\xbc\x900\xcca(\x86\xbc\x07\x97&apos;,
              &apos;credentialIdLength&apos;: 68,
              &apos;credentialPublicKey&apos;: {
                  &apos;alg&apos;: &apos;ecc&apos;,
                  &apos;eccurve&apos;: &apos;P256&apos;,
                  &apos;key&apos;: &amp;lt;cryptography.hazmat.backends.openssl.ec._EllipticCurvePublicKey object at 0x104b3fbe0&amp;gt;,
                  &apos;kty&apos;: &apos;ECP&apos;,
                  &apos;x&apos;: b&apos;\xa1\x97\xb4\xd3&apos;
                      b&apos;d\x87&amp;amp;\xbf&apos;
                      b&apos;\x8di\xb0\xfa&apos;
                      b&apos;{\xb9\x0b\x13&apos;
                      b&apos;:\xc8b\xf3&apos;
                      b&apos;\xfa3G\xcd&apos;
                      b&apos;\xdf\x08Z\xd7&apos;
                      b&apos;\x00\xd0-\xda&apos;,
                  &apos;y&apos;: b&apos;&amp;lt;\xd6\x93\x0c&apos;
                      b&apos;]\x1f\x8f\xc0&apos;
                      b&apos;\x80\xcb\x16\x0f&apos;
                      b&apos;c\x19EN&apos;
                      b&apos;y\xae\x18\x87{plw&apos;
                      b&apos;X\xd59^iK\xa4\xc0&apos;}},
  &apos;flags&apos;: {   &apos;attestedCredDataIncluded&apos;: True,
               &apos;extensionDataIncluded&apos;: False,
               &apos;userPresent&apos;: True,
               &apos;userVerified&apos;: True},
  &apos;flagsRaw&apos;: 69,
  &apos;rpIdHash&apos;: b&apos;I\x96\r\xe5\x88\x0e\x8cht4\x17\x0fdv`[\x8f\xe4\xae\xb9&apos;
              b&apos;\xa2\x862\xc7\x99\\\xf3\xba\x83\x1d\x97&apos;,
  &apos;signCount&apos;: 0}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the &lt;code&gt;attestationObject&lt;/code&gt; on newer versions of Safari instead:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;b&apos;{&quot;type&quot;:&quot;webauthn.create&quot;,&quot;challenge&quot;:&quot;mKuoQhqah7SLIxbinNzgu1jgzRGa0V0i_0Bw3WnSi3U&quot;,&quot;origin&quot;:&quot;http://localhost:5000&quot;}&apos;
format: none
attStmt: {}
{   &apos;attestedCredData&apos;: {
              &apos;aaguid&apos;: b&apos;\x00\x00\x00\x00\x00\x00\x00\x00&apos;
               b&apos;\x00\x00\x00\x00\x00\x00\x00&apos;,
              &apos;credentialId&apos;: b&apos;\xf6(\xdb5\x97^\xf9|T\x1c:\x14&apos;
                              b&apos;\xce\x95\x844\x19v+\x97&apos;,
              &apos;credentialIdLength&apos;: 20,
              &apos;credentialPublicKey&apos;: {
                  &apos;alg&apos;: &apos;ecc&apos;,
                  &apos;eccurve&apos;: &apos;P256&apos;,
                  &apos;key&apos;: &amp;lt;cryptography.hazmat.backends.openssl.ec._EllipticCurvePublicKey object at 0x104bef250&amp;gt;,
                  &apos;kty&apos;: &apos;ECP&apos;,
                  &apos;x&apos;: b&apos;h\xf8\x07)&apos;
                      b&apos;\x8c\xa4\xcc#&apos;
                      b&apos;\xc4\x80^\xda&apos;
                      b&apos;\x19#O\xcf&apos;
                      b&apos;\xc1\xc5o\xb7&apos;
                      b&quot;\x90&apos;TL)\x05r\xc6&quot;
                      b&apos;\xa2[\x9a[&apos;,
                  &apos;y&apos;: b&apos;5\xb9\xfa\x87&apos;
                      b&apos;O*\xf92\x91F\xa9~&apos;
                      b&apos;A\xf0tt&apos;
                      b&apos;\xb5\x16i\x04&apos;
                      b&apos;\xded\x06\xf6k0O3&apos;
                      b&apos;\xa8\x9a\xaa\x9a&apos;}},
  &apos;flags&apos;: {   &apos;attestedCredDataIncluded&apos;: True,
               &apos;extensionDataIncluded&apos;: False,
               &apos;userPresent&apos;: True,
               &apos;userVerified&apos;: True},
  &apos;flagsRaw&apos;: 69,
  &apos;rpIdHash&apos;: b&apos;I\x96\r\xe5\x88\x0e\x8cht4\x17\x0fdv`[\x8f\xe4\xae\xb9&apos;
              b&apos;\xa2\x862\xc7\x99\\\xf3\xba\x83\x1d\x97&apos;,
  &apos;signCount&apos;: 0}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are two main differences between the &lt;code&gt;attestationObject&lt;/code&gt; created by Chrome and Safari:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Authenticator Attestation Global Unique Identifier (&lt;strong&gt;AAGUID&lt;/strong&gt;) for Safari is zero’ed out. An AAGUID is a 128-bit identifier indicating the authenticator type. The manufacturer must &lt;a href=&quot;https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation&quot;&gt;ensure&lt;/a&gt; that the AAGUID is the same across all substantially identical keys made by that manufacturer, and different (with high probability) from the AAGUIDs of all other types of keys. Apple, by not sharing its AAGUID, makes it impossible to recognize a public key generated by an Apple device.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;attStmt&lt;/strong&gt; field is empty for the public keys generated with Safari. This field contains a cryptographically verifiable statement. For most vendors that means an array of X.509 certificates forming a chain up to Root CA for that manufacturer. Apple, starting from iOS 16 and Mac OS Ventura, makes it impossible to verify the authenticity of the attestation generated via Safari, because the authenticator doesn’t generate any attestation statement.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These two changes are meaningful because it is now impossible to verify the device type used to generate a key, and hence the trustworthiness of the key and its metadata. In fact, the lack of an attestation statement means that you can&apos;t prove the key has been stored, or even generated, safely because you can&apos;t infer properties/provenance of the authenticator.&lt;/p&gt;
&lt;h3&gt;How are passkeys stored vs Platform authenticator keys&lt;/h3&gt;
&lt;p&gt;Passkeys and traditional platform authenticator keys are generated and stored in an Apple-specific trusted secure subsystem called Secure Enclave Processor (SEP), which is separate from the main processor and which is capable of preserving the integrity of the data even in the event of a device compromise.&lt;/p&gt;
&lt;p&gt;The bar to compromise the Secure Enclave is extremely high, and therefore the most sensitive data types such as Apple Pay and FaceID data are stored there. This presentation has a great in-depth explanation of &lt;a href=&quot;https://www.blackhat.com/docs/us-16/materials/us-16-Mandt-Demystifying-The-Secure-Enclave-Processor.pdf&quot;&gt;how SEP works&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unlocking access to a WebAuthn credential requires users to confirm their identity either through FaceID or TouchID (depending on the device model). In both cases, the communication between the biometric sensor and the Secure Enclave happens over a physical serial peripheral bus over an encrypted channel, and the data never leaves the Secure Enclave, where it is verified against a 2D representation of the original biometric data.
This guide from Apple has further details on SEP-based creation and storage of &lt;a href=&quot;https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/protecting_keys_with_the_secure_enclave&quot;&gt;keys&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is an example of how Chrome creates WebAuthn keys through SEP:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;base::ScopedCFTypeRef&amp;lt;SecAccessControlRef&amp;gt;
TouchIdCredentialStore::DefaultAccessControl() {
 return base::ScopedCFTypeRef&amp;lt;SecAccessControlRef&amp;gt;(
     SecAccessControlCreateWithFlags(
         kCFAllocatorDefault,
         // Credential can only be used when the device is unlocked.
         kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
         // Private key is available for signing after user authorization with
         // biometrics or password.
         kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence,
         nullptr));
}
absl::optional&amp;lt;std::pair&amp;lt;Credential, base::ScopedCFTypeRef&amp;lt;SecKeyRef&amp;gt;&amp;gt;&amp;gt;
TouchIdCredentialStore::CreateCredential(
  const std::string&amp;amp; rp_id,
  const PublicKeyCredentialUserEntity&amp;amp; user,
  Discoverable discoverable) const {
…
CFDictionarySetValue(params, kSecAttrSynchronizable, @NO);
CFDictionarySetValue(params, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave);
 …
CFDictionarySetValue(private_key_params, kSecAttrIsPermanent, @YES);
 CFDictionarySetValue(private_key_params, kSecAttrAccessControl,
                      DefaultAccessControl());
…
base::ScopedCFTypeRef&amp;lt;CFErrorRef&amp;gt; cferr;
base::ScopedCFTypeRef&amp;lt;SecKeyRef&amp;gt; private_key =
    Keychain::GetInstance().KeyCreateRandomKey(params,
                                               cferr.InitializeInto());
…
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Crucially, note how Chrome uses three parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;kSecAttrSynchronizable&lt;/em&gt; is set to NO. In other words, this key cannot be synchronized through iCloud.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;kSecAttrTokenIDSecureEnclave&lt;/em&gt; calls &lt;em&gt;KeyCreateRandomKey&lt;/em&gt; to generate the key directly in SEP.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;kSecAttrAccessibleWhenUnlockedThisDeviceOnly&lt;/em&gt; is specified, which forces the key to be encrypted with a resident device key that can’t be migrated to iCloud. Per Apple, &lt;strong&gt;&quot;This is recommended for items that need to be accessible only while the application is in the foreground. Items with this attribute do not migrate to a new device. Thus, after restoring from a backup of a different device, these items will not be present.&quot;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So what about Safari? Apple clarifies in their documentation that Safari keychain items are synchronized.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As a result, every item that will sync must be explicitly marked with the &lt;code&gt;kSecAttrSynchronizable&lt;/code&gt; attribute. Apple sets this attribute for Safari user data (including user names, passwords, and credit card numbers), as well as for Wi-Fi passwords, HomeKit encryption keys, and other keychain items supporting end-to-end iCloud encryption.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Safari codebase is confusing with respect to key creation. On the surface, it looks like &lt;code&gt;kSecAttrSynchronizable&lt;/code&gt; is not specified anywhere:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RetainPtr&amp;lt;SecKeyRef&amp;gt; LocalConnection::createCredentialPrivateKey(LAContext *context, SecAccessControlRef accessControlRef, const String&amp;amp; secAttrLabel, NSData *secAttrApplicationTag) const
{
  RetainPtr privateKeyAttributes = @{
      (id)kSecAttrAccessControl: (id)accessControlRef,
      (id)kSecAttrIsPermanent: @YES,
      (id)kSecAttrAccessGroup: @(LocalAuthenticatorAccessGroup),
      (id)kSecAttrLabel: secAttrLabel,
      (id)kSecAttrApplicationTag: secAttrApplicationTag,
  };
  if (context) {
      auto mutableCopy = adoptNS([privateKeyAttributes mutableCopy]);
      mutableCopy.get()[(id)kSecUseAuthenticationContext] = context;
      privateKeyAttributes = WTFMove(mutableCopy);
  }
  NSDictionary *attributes = @{
      (id)kSecAttrTokenID: (id)kSecAttrTokenIDSecureEnclave,
      (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
      (id)kSecAttrKeySizeInBits: @256,
      (id)kSecPrivateKeyAttrs: privateKeyAttributes.get(),
  };
  LOCAL_CONNECTION_ADDITIONS
  CFErrorRef errorRef = nullptr;
  auto credentialPrivateKey = adoptCF(SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &amp;amp;errorRef));
  auto retainError = adoptCF(errorRef);
  if (errorRef) {
      LOG_ERROR(&quot;Couldn&apos;t create private key: %@&quot;, (NSError *)errorRef);
      return nullptr;
  }
  return credentialPrivateKey;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, paying closer attention, you’ll notice a macro in the code called &lt;code&gt;LOCAL_CONNECTION_ADDITIONS&lt;/code&gt;. This cryptic macro is defined as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#if USE(APPLE_INTERNAL_SDK)
#import &amp;lt;WebKitAdditions/LocalConnectionAdditions.h&amp;gt;
#else
#define LOCAL_CONNECTION_ADDITIONS
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking at the disassembled function generated through Ghidra, we can see what actually happens:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void __ZNK6WebKit15LocalConnection26createCredentialPrivateKeyEP9LAContextP18__SecAccessControlRKN3W TF6StringEP6NSData
              (undefined8 param_1,long param_2,undefined8 param_3,long *param_4,undefined8 param_5)
{
 …
 (&amp;amp;_OBJC_CLASS_$_NSString,&quot;stringWithUTF8String:&quot;,&quot;com.apple.webkit.webauthn&quot;);
 ...
 uVar7 = *(undefined8 *)__got::_kSecAttrLabel;
 …
 local_180 = *(undefined8 *)__got::_kSecAttrTokenID;
 local_160 = *(undefined8 *)__got::_kSecAttrTokenIDSecureEnclave;
 uVar9 = *(undefined8 *)__got::_kSecAttrKeyType;
 uVar8 = *(undefined8 *)__got::_kSecAttrKeyTypeECSECPrimeRandom;
 uVar13 = *(undefined8 *)__got::_kSecAttrKeySizeInBits;
 uVar11 = *(undefined8 *)__got::_kSecPrivateKeyAttrs;
 local_150 = &amp;amp;PTR__OBJC_CLASS_$_NSConstantIntegerNumber_00c3a9f0;
 uStack376 = uVar9;
 local_170 = uVar13;
 uStack360 = uVar11;
 uStack344 = uVar8;
 lStack328 = lVar3;
 uVar4 = __auth_stubs::_objc_msgSend
                   (&amp;amp;_OBJC_CLASS_$_NSDictionary,&quot;dictionaryWithObjects:forKeys:count:&quot;,&amp;amp;local_160,
                    &amp;amp;local_180,4);
 lVar2 = (*(code *)__ZN6WebKit27getASCWebKitSPISupportClassE)();
 if (lVar2 != 0) {
   uVar5 = (*(code *)__ZN6WebKit27getASCWebKitSPISupportClassE)();
   iVar1 = __auth_stubs::_objc_msgSend(uVar5,&quot;shouldUseAlternateCredentialStore&quot;);
   if (iVar1 != 0) {
     local_b0 = *(undefined8 *)__got::_kSecAttrSynchronizable;
     local_90 = __got::___kCFBooleanTrue;
     local_80 = &amp;amp;PTR__OBJC_CLASS_$_NSConstantIntegerNumber_00c3a9f0;
     local_d0 = __got::___kCFBooleanTrue;
     local_f0 = uVar10;
     uStack232 = uVar6;
     uStack168 = uVar9;
     local_a0 = uVar13;
     uStack152 = uVar11;
     uStack136 = uVar8;
     local_c8 = __auth_stubs::_objc_msgSend
                          (&amp;amp;_OBJC_CLASS_$_NSString,&quot;stringWithUTF8String:&quot;,
                           &quot;com.apple.webkit.webauthn&quot;);
     local_e0 = uVar7;
     if (*param_4 == 0) {
       local_c0 = &amp;amp;cf_&quot;&quot;;
     }
     else {
       local_c0 = (cfstringStruct *)__auth_stubs::__ZN3WTF10StringImplcvP8NSStringEv();
     }
     local_d8 = uVar12;
     uStack184 = param_5;
     local_78 = __auth_stubs::_objc_msgSend
                          (&amp;amp;_OBJC_CLASS_$_NSDictionary,&quot;dictionaryWithObjects:forKeys:count:&quot;,
                           &amp;amp;local_d0,&amp;amp;local_f0,4);
     uVar4 = __auth_stubs::_objc_msgSend
                       (&amp;amp;_OBJC_CLASS_$_NSDictionary,&quot;dictionaryWithObjects:forKeys:count:&quot;,
                        &amp;amp;local_90,&amp;amp;local_b0,4);
   }
 }
 local_90 = (undefined *)0x0;
 lVar2 = __auth_stubs::_SecKeyCreateRandomKey(uVar4,&amp;amp;local_90);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In other words, when Safari is built with the internal &lt;code&gt;APPLE_INTERNAL_SDK&lt;/code&gt; the dictionary passed to &lt;code&gt;SecKeyCreateRandomKey&lt;/code&gt; is modified to include &lt;code&gt;kSecAttrSynchronizable&lt;/code&gt; as a parameter, making the key exportable.&lt;/p&gt;
&lt;p&gt;You might have noticed that in both code snippets a private key object is returned, even though it’s created by the Secure Enclave. The private key is logically part of the keychain, and you can later obtain a reference to it in the usual way, but the key data is encoded and not available in clear-text, and only the Secure Enclave can use the key.&lt;/p&gt;
&lt;h3&gt;How are keys exported from the Secure Enclave?&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, the purpose of storing sensitive data in the Secure Enclave is to avoid exposing private keys outside of the secure processor, so a reasonable question is: how exactly are keys exported from the SEP to iCloud?&lt;/p&gt;
&lt;p&gt;As mentioned above, &lt;code&gt;SecKeyCreateRandomKey&lt;/code&gt; returns a reference to the private key but, when the key is generated in the Secure Enclave, &lt;code&gt;SecKeyCreateRandomKey&lt;/code&gt; returns an encoded key instead of a clear-text private key.&lt;/p&gt;
&lt;p&gt;Normally these keys are encrypted with a Secure Enclave keypair that never leaves the device. However, if an item is marked as &lt;code&gt;kSecAttrSynchronizable&lt;/code&gt;, the Secure Enclave will use a different keypair to encrypt the key.
Quoting Apple:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When a user enables iCloud Keychain for the first time, the device establishes a circle of trust and creates a syncing identity for itself. The syncing identity consists of a private key and a public key, and is stored in the device’s keychain. The public key of the syncing identity is put in the circle, and the circle is signed twice: first by the private key of the syncing identity, and then again with an asymmetric elliptical key (using P-256) derived from the user’s iCloud account password. Also stored with the circle are the parameters (random salt and iterations) used to create the key that’s based on the user’s iCloud password.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In other words, the private key used to encrypt &lt;code&gt;kSecAttrSynchronizable&lt;/code&gt; keys in the Secure Enclave is backed in iCloud. As such, when a new device needs to restore the keychain it can reconstruct the private key and thus decrypt the keypair needed to access all the private keys marked as &lt;code&gt;kSecAttrSynchronizable&lt;/code&gt;, which include passkeys.&lt;/p&gt;
&lt;h3&gt;How does iCloud Keychain keep data safe?&lt;/h3&gt;
&lt;p&gt;As we’ve seen, the security of passkeys relies on the security of the iCloud Keychain. Apple does a great job at explaining how iCloud Keychain data can be accessed and its recovery process.&lt;/p&gt;
&lt;p&gt;The brief summary of the storage model is that the iCloud Keychain data is encrypted with an hardware-bound keypair, stored in an hardware security module (HSM). The key is inaccessible to Apple. The encrypted keypair is then stored with Apple. To quote the documentation&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The iOS, iPadOS, or macOS device first exports a copy of the user’s keychain and then encrypts it wrapped with keys in an asymmetric keybag and places it in the user’s iCloud key-value storage area. The keybag is wrapped with the user’s iCloud security code and with the public key of the hardware security module (HSM) cluster that stores the escrow record. This becomes the user’s iCloud escrow record. For two-factor authentication accounts, the keychain is also stored in CloudKit and wrapped to intermediate keys that are recoverable only with the contents of the iCloud escrow record, thereby providing the same level of protection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The recovery process requires the user to have access to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Authenticate to their iCloud account&lt;/li&gt;
&lt;li&gt;Receive an SMS code on the user phone number saved for recovery&lt;/li&gt;
&lt;li&gt;The passcode of one of the devices&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Apple implements various other mechanisms to reduce the risk of credential stuffing attacks.&lt;/p&gt;
&lt;p&gt;More information &lt;a href=&quot;https://support.apple.com/en-gb/guide/security/sec3e341e75d/web&quot;&gt;here&lt;/a&gt;, &lt;a href=&quot;https://support.apple.com/en-us/HT213305&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://support.apple.com/guide/security/secure-keychain-syncing-sec0a319b35f/web&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Passkeys are a significant step forward in the journey to go passwordless at scale. Even though their security profile is weaker than hardware-bound WebAuthn keys, Apple has a strong recovery process for iCloud Keychain, which partially mitigates the associated risks and the main security benefit of WebAuthn, phishing protection, is preserved with passkeys.&lt;/p&gt;
&lt;p&gt;However, the changes introduced by Apple to implement passkeys make both verifying the provenance and storage of the key and the creation of hardware-bound iOS keys impossible, significantly reducing the scope of WebAuthn security and integrity guarantees.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Synchronous Webhooks</title><link>https://slashid.dev/blog/sync-webhooks/</link><guid isPermaLink="true">https://slashid.dev/blog/sync-webhooks/</guid><description>We are excited to release synchronous webhooks, the latest addition to our webhooks features.  With synchronous webhooks, you can extend SlashID Access to suit your business needs in a few simple steps, in whatever language and environment makes sense for you.</description><pubDate>Thu, 20 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We are excited to release synchronous webhooks, the latest addition to our webhooks features. With synchronous webhooks, you can extend SlashID Access to suit your business needs in a few simple steps, in whatever language and environment makes sense for you.&lt;/p&gt;
&lt;p&gt;In this blogpost, we will introduce synchronous webhooks by answering three questions – why, what, and how, including a short example.&lt;/p&gt;
&lt;h2&gt;The Why&lt;/h2&gt;
&lt;p&gt;Our initial &lt;a href=&quot;../webhooks&quot;&gt;webhooks release&lt;/a&gt; focused on asynchronous webhooks triggered by events, which are ideal for analytics and monitoring.&lt;/p&gt;
&lt;p&gt;However, there are many use cases where you need to extend the identity logic in some business-specific way. For example, you may need to enrich an authentication token with information you keep internally, or ensure that certain actions are included in your internal audit log.&lt;/p&gt;
&lt;p&gt;With SlashID synchronous webhooks, you can quickly and easily integrate your custom identity logic into SlashID’s existing flows, using the language and deployment environment that makes the most sense for you and your organization. You gain visibility into crucial identity flows, and have the opportunity to affect their outcomes.&lt;/p&gt;
&lt;h2&gt;The What&lt;/h2&gt;
&lt;p&gt;Synchronous hooks are the extension points in SlashID flows where your webhooks will be called. Each type of synchronous hook has its own content, and accepts specific responses that can be used to affect the relevant flows.&lt;/p&gt;
&lt;p&gt;For this initial release, we have focused on hooks when the user registers or signs-in. Specifically, we hook the point during sign-in after authentication has succeeded, but before we sign and issue the authentication token to the user. The hook content contains the claims that will be present in the issued token. The response from your webhook can optionally contain custom claims as key-value pairs, which will be added as claims in the token before it is issued.&lt;/p&gt;
&lt;p&gt;This hook allows you to change the authentication/registration flow and add your custom business logic before the token is returned to the requesting application. Crucially, the webhook is executed before the token is signed to avoid potential security concerns.&lt;/p&gt;
&lt;h2&gt;The How&lt;/h2&gt;
&lt;p&gt;Synchronous webhooks build on SlashID’s existing webhooks functionality – so you can use exactly the same APIs to manage your webhooks, and the webhook requests follow the same pattern. You will need to implement the handler for your webhook endpoint, and then you’re ready to start receiving synchronous hook requests.&lt;/p&gt;
&lt;p&gt;Let’s see how this works in practice.&lt;/p&gt;
&lt;p&gt;First, you need to implement the API for handling webhook requests. In this example, we will fetch some personal details about the user logging in and use these to enrich the token. For brevity, we have omitted error handling.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import (
	&quot;encoding/json&quot;
	&quot;io&quot;
	&quot;net/http&quot;
	&quot;net/url&quot;

	&quot;github.com/google/tink/go/jwt&quot;
)

const (
	sidOrgID       = &quot;my-slashid-organization-id&quot;
	sidBaseURL     = &quot;https://api.slashid.com&quot;
	svcBaseURL     = &quot;https://my-service.com&quot;
	sidWebhookPath = &quot;/sid/webhook&quot;
)

func HandleSlashIDWebhook(r *http.Request, w http.ResponseWriter) {
	// Read the body from the request - this will be a signed and encoded JWT
	reqBody, _ := io.ReadAll(r.Body)
	defer r.Body.Close()

	// Retrieve the verification key for your organization using the SlashID APIs
	verificationJWKS := getVerificationJWKS()
	verificationKeyset, _ := jwt.JWKSetToPublicKeysetHandle(verificationJWKS)

	// Build the verifier and validator
	verifier, _ := jwt.NewVerifier(verificationKeyset)

	issuer := sidBaseURL
	aud := sidOrgID
	validator, _ := jwt.NewValidator(&amp;amp;jwt.ValidatorOpts{
		ExpectedIssuer:         &amp;amp;issuer,
		AllowMissingExpiration: false,
		ExpectedAudience:       &amp;amp;aud,
	})

	// Verify the JWT - this will include the issuer, audience, and expiration
	verifiedJWT, err := verifier.VerifyAndDecode(string(reqBody), validator)
	if err != nil {
		// The request body cannot be verified as coming from SlashID and so should not be trusted!
	}

	// Check the target URL matches the endpoint this handler is associated with
	targetURL, _ := verifiedJWT.StringClaim(&quot;target_url&quot;)
	expectedTargetURL, _ := url.JoinPath(svcBaseURL, sidWebhookPath)
	if targetURL != expectedTargetURL {
		// This URL does not seem to be the intended target - ignore, log
	}

	// We are happy that the webhook request is legitimate, so we can handle the actual content
	// In this case, we are expecting a token minted sync hook, so the trigger content is the
	// claims that will appear in the user token SlashID is about to issue to the authenticated user.
	userTokenClaims, _ := verifiedJWT.ObjectClaim(&quot;trigger_content&quot;)
	personID := userTokenClaims[&quot;sub&quot;]

	// Here we have your business logic to retrieve name (string) and address (array of strings)
	name, address := getPersonalDetails(personID.(string))

	// Write these as JSON in the response body
	respMap := map[string]any{
		&quot;name&quot;:    name,
		&quot;address&quot;: address,
	}
	respBody, _ := json.Marshal(respMap)
	_, _ = w.Write(respBody)

	return
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we will make two API calls to SlashID - one to &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-organizations-webhooks&quot;&gt;create a webhook&lt;/a&gt;, and one to &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-organizations-webhook-id-triggers&quot;&gt;create a trigger&lt;/a&gt; for that webhook.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/organizations/webhooks&apos; \
--header &apos;SlashID-OrgID: my-slashid-organization-id&apos; \
--header &apos;SlashID-API-Key: my-slashid-api-key&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;target_url&quot;: &quot;https://my-service.com/sid/webhook&quot;,
    &quot;name&quot;: &quot;token minted webhook&quot;,
}&apos;


{
  &quot;result&quot;: {
    &quot;id&quot;: &quot;e64c7123-497d-7e12-b665-341b5defdec1&quot;,
    &quot;target_url&quot;: &quot;https://my-service.com/sid/webhook&quot;,
  }
}


&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &apos;https://api.slashid.com/organizations/webhooks/e64c7123-497d-7e12-b665-341b5defdec1/triggers&apos; \
--header &apos;SlashID-OrgID: my-slashid-organization-id&apos; \
--header &apos;SlashID-API-Key: my-slashid-api-key&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;trigger_type&quot;: &quot;sync_hook&quot;,
    &quot;trigger_name&quot;: &quot;token_minted&quot;
}&apos;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now suppose a user authenticates to your application via SlashID, using an email link.&lt;/p&gt;
&lt;p&gt;Once they have successfully authenticated, the SlashID backend prepares a token. Before the token is signed and issued back to the user, SlashID reaches the hook point and checks for webhooks registered for this trigger. It finds the webhook you created earlier, and so your webhook is called.&lt;/p&gt;
&lt;p&gt;The body will be a signed JWT, which your handler function verifies and decodes. If we decode the JWT payload, we see that the trigger content contains the claims that will be present in the user token:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;aud&quot;: &quot;my-slashid-organization-id&quot;,
  &quot;iss&quot;: &quot;https://slashid.local&quot;,
  &quot;target_url&quot;: &quot;https://my-service.com/sid/webhook&quot;,
  &quot;trigger_name&quot;: &quot;token_minted&quot;,
  &quot;trigger_type&quot;: &quot;sync_hook&quot;,
  &quot;webhook_id&quot;: &quot;e64c7123-497d-7e12-b665-341b5defdec1&quot;,

  &quot;trigger_content&quot;: {
    &quot;aud&quot;: &quot;my-slashid-organization-id&quot;,
    &quot;authentications&quot;: [
      {
        &quot;handle&quot;: {
          &quot;type&quot;: &quot;email_address&quot;,
          &quot;value&quot;: &quot;alex.singh@slashid.dev&quot;
        },
        &quot;method&quot;: &quot;email_link&quot;,
        &quot;timestamp&quot;: &quot;2023-07-18T12:25:56.390080959Z&quot;
      }
    ],
    &quot;first_token&quot;: false,
    &quot;groups&quot;: [&quot;admins&quot;],
    &quot;groups_claim_name&quot;: &quot;groups&quot;,
    &quot;iss&quot;: &quot;https://slashid.local&quot;,
    &quot;region&quot;: &quot;us-iowa&quot;,
    &quot;sub&quot;: &quot;165c7138-545d-7065-8ff2-13850368729&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your handler retrieves the personal information, and returns it in the response. The SlashID backend uses this response to enrich the token, which is then signed and issued to the user. The final token payload will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;aud&quot;: &quot;my-slashid-organization-id&quot;,
  &quot;authentications&quot;: [
    {
      &quot;handle&quot;: {
        &quot;type&quot;: &quot;email_address&quot;,
        &quot;value&quot;: &quot;alex.singh@example.com&quot;
      },
      &quot;method&quot;: &quot;email_link&quot;,
      &quot;timestamp&quot;: &quot;2023-07-18T12:25:56.390080959Z&quot;
    }
  ],
  &quot;first_token&quot;: false,
  &quot;groups&quot;: [&quot;admins&quot;],
  &quot;groups_claim_name&quot;: &quot;groups&quot;,
  &quot;iss&quot;: &quot;https://slashid.local&quot;,
  &quot;region&quot;: &quot;us-iowa&quot;,
  &quot;sub&quot;: &quot;165c7138-545d-7065-8ff2-13850368729&quot;,
  &quot;name&quot;: &quot;Alex Singh&quot;,
  &quot;address&quot;: [&quot;123 Main Street&quot;, &quot;Honolulu&quot;, &quot;Hawaii&quot;, &quot;96801&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It contains the claims that your webhook received in the request, plus those returned in the response.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;With this release, you can use synchronous webhooks to extend SlashID Access. We will be adding more hooks and features in the near future, so stay tuned for release announcements.&lt;/p&gt;
&lt;p&gt;Ready to try SlashID? Register &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=syncwebhook-bp&quot;&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 webhooks and have some feedback? &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;Let us know&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Joseph Gardner, Vincenzo Iozzo</author></item><item><title>SlashID SDK for PHP and Laravel authentication</title><link>https://slashid.dev/blog/php-laravel-authentication/</link><guid isPermaLink="true">https://slashid.dev/blog/php-laravel-authentication/</guid><description>While very popular, PHP lacks modern identity and access management (IAM) capabilities. SlashID changes that with the release of our SDK for PHP and Laravel.  This is just the beginning; according to W3Tech PHP is used by over 76% of indexed websites. In the weeks to come, we aim to cover other popular frameworks such as Drupal and Symfony.</description><pubDate>Tue, 02 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;PHP is a foundational language and many websites and frameworks are built on it, notoriously WordPress and Facebook. According to &lt;a href=&quot;https://w3techs.com/technologies/details/pl-php&quot;&gt;W3Tech&lt;/a&gt;, PHP is used in over 76% of indexed websites out there.&lt;/p&gt;
&lt;p&gt;At SlashID, we love to support the latest frameworks and technologies such as our &lt;a href=&quot;https://developer.slashid.dev/docs/access/remix-sdk/tutorials/quick-start&quot;&gt;Remix SDK&lt;/a&gt;, but our mission is to solve real problems for real people. Given the importance of PHP in the ecosystem and the relative lack of modern authentication libraries available, we decided to fill that gap and release a PHP library coupled with a Laravel library.&lt;/p&gt;
&lt;p&gt;Laravel is one of the more popular PHP frameworks out there but not the only one: in the next few weeks, we&apos;ll release support for Symfony, Drupal, and more.&lt;/p&gt;
&lt;h2&gt;Design principles&lt;/h2&gt;
&lt;p&gt;Our core PHP SDK serves as the base for all the frameworks integrations. At a high level, it provides a wrapper around the SlashID &lt;a href=&quot;https://developer.slashid.dev/docs/api&quot;&gt;APIs&lt;/a&gt;.
But the SDK goes beyond that and provides several useful abstractions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Errors in the APIs are mapped to &lt;a href=&quot;https://github.com/slashid/php/blob/main/README.md#exceptions&quot;&gt;PHP exceptions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A SlashID user is mapped to the &lt;a href=&quot;https://github.com/slashid/php/blob/main/README.md#slashidphppersoninterface--slashidphpperson&quot;&gt;Person class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An abstraction to &lt;a href=&quot;https://github.com/slashid/php/blob/main/README.md#migration-abstraction&quot;&gt;migrate users&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An abstraction for the &lt;a href=&quot;https://github.com/slashid/php/blob/main/README.md#webhook-abstraction&quot;&gt;SlashID webhooks&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Laravel quickstart&lt;/h2&gt;
&lt;p&gt;Now that we covered the design principle of the core PHP SDK, let&apos;s see how to deploy SlashID with Laravel.&lt;/p&gt;
&lt;h3&gt;1. Install the Laravel SlashID package&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;composer require slashid/laravel
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Edit the environment file&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;SLASHID_ENVIRONMENT: either &quot;sandbox&quot; or &quot;production&quot;&lt;/li&gt;
&lt;li&gt;SLASHID_ORGANIZATION_ID: your organization&apos;s ID. You&apos;ll find it in your SlashID console (https://console.slashid.dev/ for production, https://console.sandbox.slashid.dev/ for sandbox), in the &quot;Settings&quot; tab, at the top of the page.&lt;/li&gt;
&lt;li&gt;SLASHID_API_KEY: your organization&apos;s API Key. You&apos;ll also find it in your SlashID console, in the &quot;Settings&quot; tab, at the very bottom of the page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&apos;s an example configuration for your &lt;code&gt;.env&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SLASHID_ENVIRONMENT=sandbox
SLASHID_ORGANIZATION_ID=412edb57-ae26-f2aa-9999-770021ed64a0
SLASHID_API_KEY=z0dlY-nluiq8mcvm8YTolSkJV678
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Publish the resource&lt;/h3&gt;
&lt;p&gt;Run the following artisan command to publish the resources:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;php artisan vendor:publish --provider=&quot;SlashId\Laravel\Providers\SlashIdServiceProvider&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&apos;re ready! Now access /login in your website and enjoy your new login with SlashID.&lt;/p&gt;
&lt;h3&gt;4. Customize the login flow&lt;/h3&gt;
&lt;p&gt;The Laravel package comes with a bundle of the &lt;a href=&quot;https://developer.slashid.dev/docs/access/react-sdk&quot;&gt;SlashID React SDK&lt;/a&gt; and a small JavaScript glue piece of code in &lt;code&gt;vendor/slashid/laravel/public/slashid.laravel-web-login.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are two ways to customize the flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Change the login form: the SlashID login form is rendered in two Blade templates: &lt;code&gt;slashid/login.blade.php&lt;/code&gt; and &lt;code&gt;slashid/login-form.blade.php&lt;/code&gt;. If you want to wrap the login form in &lt;code&gt;/login&lt;/code&gt; inside the layout of the page, you can override the &lt;code&gt;login&lt;/code&gt; template as shown &lt;a href=&quot;https://github.com/slashid/laravel/blob/main/README.md#overriding-the-login-form&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Customize the bundled React SDK as shown &lt;a href=&quot;https://github.com/slashid/laravel/blob/main/README.md#overriding-the-login-form&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Bonus: migrate users with ease&lt;/h2&gt;
&lt;p&gt;If you are installing SlashID in an existing Laravel website, you may already have a user base that you&apos;ll want to migrate to SlashID&apos;s database. This is made easy with two migration commands.&lt;/p&gt;
&lt;h3&gt;1. Run the migration script&lt;/h3&gt;
&lt;p&gt;First, you have to run the artisan command &lt;code&gt;php artisan slashid:import:create-script&lt;/code&gt;. It will ask you the User class in your installation, usually &lt;code&gt;\App\Models\User&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ php artisan slashid:import:create-script

 Please inform the class of the user model [\App\Models\User]:
 &amp;gt;

The Slash ID migration script has been created at /var/www/html/database/slashid/user-migration.php. Please open the file and modify it according to the instructions in it.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Adapt the generated script&lt;/h3&gt;
&lt;p&gt;A script will be created in &lt;code&gt;database/slashid/user-migration.php&lt;/code&gt;. It will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

use SlashId\Laravel\SlashIdUser;

/** @var \Illuminate\Contracts\Auth\Authenticatable[] */
$laravelUsers = \App\Models\User::all();
$slashIdUsers = [];
foreach ($laravelUsers as $laravelUser) {
    $slashIdUser = new SlashIdUser();
    $slashIdUser
        -&amp;gt;addEmailAddress($laravelUser-&amp;gt;email)
        -&amp;gt;setLegacyPasswordToMigrate($laravelUser-&amp;gt;getAuthPassword())
        // Uncomment if you want to set the phone number.
        // -&amp;gt;addPhoneNumber($laravelUser-&amp;gt;phone_number)
        // Uncomment if you want to set groups.
        // -&amp;gt;setGroups([&apos;Editor&apos;])
        // Uncomment if you want to specify a region for the user.
        // -&amp;gt;setRegion(&apos;us-iowa&apos;)
        -&amp;gt;setBucketAttributes(\SlashId\Php\PersonInterface::BUCKET_ORGANIZATION_END_USER_NO_ACCESS, [
            // List the user attributes you want to migrate, grouped by bucket.
            &apos;old_id&apos; =&amp;gt; $laravelUser-&amp;gt;getAuthIdentifier(),
            &apos;firstname&apos; =&amp;gt; $laravelUser-&amp;gt;firstname,
            &apos;email_verified_at&apos; =&amp;gt; $laravelUser-&amp;gt;email_verified_at,
            &apos;lastname&apos; =&amp;gt; $laravelUser-&amp;gt;lastname,
            &apos;username&apos; =&amp;gt; $laravelUser-&amp;gt;username,
        ]);

    $slashIdUsers[] = $slashIdUser;
}

return $slashIdUsers;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You must change &lt;code&gt;user-migration.php&lt;/code&gt; to model the data to be migrated as you want. The script must return an array of &lt;code&gt;\SlashId\Laravel\SlashIdUser&lt;/code&gt; with all the users you want to bulk import into SlashID.&lt;/p&gt;
&lt;h3&gt;3. Execute the migration&lt;/h3&gt;
&lt;p&gt;After adapting the script to your needs, run &lt;code&gt;php artisan slashid:import:run&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ php artisan slashid:import:run
+------------------------+---------------+--------+-------+--------+-------------------------------------------------------------------------------------------------------------------------------+
| Emails                 | Phone numbers | Region | Roles | Groups | Attributes                                                                                                                    |
+------------------------+---------------+--------+-------+--------+-------------------------------------------------------------------------------------------------------------------------------+
| rattazzi@example.com   |               |        |       |        | {&quot;end_user_no_access&quot;:{&quot;old_id&quot;:1,&quot;firstname&quot;:&quot;Urbano&quot;,&quot;email_verified_at&quot;:null,&quot;lastname&quot;:&quot;Rattazzi&quot;,&quot;username&quot;:&quot;rattazzi&quot;}} |
| nitti@example.com      |               |        |       |        | {&quot;end_user_no_access&quot;:{&quot;old_id&quot;:2,&quot;firstname&quot;:&quot;Francesco&quot;,&quot;email_verified_at&quot;:null,&quot;lastname&quot;:&quot;Nitti&quot;,&quot;username&quot;:&quot;nitti&quot;}}    |
| cavour@example.com     |               |        |       |        | {&quot;end_user_no_access&quot;:{&quot;old_id&quot;:3,&quot;firstname&quot;:&quot;Camillo&quot;,&quot;email_verified_at&quot;:null,&quot;lastname&quot;:&quot;Cavour&quot;,&quot;username&quot;:&quot;cavour&quot;}}    |
+------------------------+---------------+--------+-------+--------+-------------------------------------------------------------------------------------------------------------------------------+

 Do you want to proceed with importing 3 users? (yes/no) [no]:
 &amp;gt; yes

2 successfully imported users.
1 user failed to import. Check the file /var/www/html/database/slashid/migration-failed-202403271142.csv for errors.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any errors that occur in a migration will be output as a CSV. Check the CSV to fix the errors and run again.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this brief blog post, we have introduced our idiomatic PHP core SDK and the SlashID Laravel SDK. If you&apos;re ready to upgrade your PHP code base to a modern authentication experience, SlashID is here to assist. Don&apos;t hesitate to &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;reach out to us&lt;/a&gt; or sign up for free &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=php&quot;&gt;here&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>In-browser HSM-backed Encryption with Tink and Wasm</title><link>https://slashid.dev/blog/tink-wasm/</link><guid isPermaLink="true">https://slashid.dev/blog/tink-wasm/</guid><description>This post explores how to use  Wasm to lift Tink to JavaScript and how you can leverage it to perform client-side encryption directly from the browser, backed with a master key stored in a HSM.</description><pubDate>Sun, 18 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;We already wrote about our app-layer &lt;a href=&quot;https://www.slashid.dev/blog/app-layer-encryption/&quot;&gt;encryption model&lt;/a&gt; and how we use it to prevent mass-exfiltration and keep user data safe. However, with our model the end user still has to trust our ability to keep data safe and secure.&lt;/p&gt;
&lt;p&gt;The natural evolution of this is to allow the user to perform client-side encryption so that SlashID is unable to decrypt any data stored with us. Client-side encryption involves several building blocks and has a number of tradeoffs that make it unsuitable for some use cases.&lt;/p&gt;
&lt;p&gt;This blogpost is an experiment to show what is possible with Wasm and Tink, rather than a design recommendation. In particular we’ll show a proof of concept of how to encrypt data directly from the browser using a master key stored in a Hardware Security Module (HSM) using Tink (Google’s open-source cryptography library) and WebAssembly (Wasm), but it is not meant as a recommendation on how to implement secure client-side encryption.&lt;/p&gt;
&lt;p&gt;It is also worth mentioning that client-side encryption is significantly easier and safer in native environments such as iOS apps, and we expect that some of those capabilities (for example, the ability to encrypt/decrypt data through the Secure Enclave) will eventually be exposed to the browser through Web Crypto.&lt;/p&gt;
&lt;h2&gt;Web Crypto vs Tink&lt;/h2&gt;
&lt;p&gt;While &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API&quot;&gt;Web Crypto APIs&lt;/a&gt; have now reached over 97% coverage in &lt;a href=&quot;https://caniuse.com/cryptography&quot;&gt;browsers&lt;/a&gt;, Mozilla is the first one to warn that the use of these primitives is very challenging and with a potentially disastrous impact on security:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Warning: The Web Crypto API provides a number of low-level cryptographic primitives. It&apos;s very easy to misuse them, and the pitfalls involved can be very subtle.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Even assuming you use the basic cryptographic functions correctly, secure key management and overall security system design are extremely hard to get right, and are generally the domain of specialist security experts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Errors in security system design and implementation can make the security of the system completely ineffective.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We fully agree with the Mozilla folks! In fact, while it is certainly possible to use native Web Crypto to implement end-to-end encryption (here’s an &lt;a href=&quot;https://blog.excalidraw.com/end-to-end-encryption/&quot;&gt;example&lt;/a&gt;), we are big proponents of using a safer abstraction layer on top of low-level cryptographic primitives.&lt;/p&gt;
&lt;p&gt;Furthermore, Web Crypto has limited support for cryptographic primitives. For instance, neither deterministic encryption nor streaming encryption are supported.&lt;/p&gt;
&lt;p&gt;Tink provides a safer layer of abstraction above underlying cryptographic primitives and has support for several cryptographic primitives including Envelope Encryption and Deterministic Encryption, that’s why we chose it for our &lt;a href=&quot;https://www.slashid.dev/blog/app-layer-encryption/&quot;&gt;app-layer encryption model&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Given our positive experience with Tink, we wanted to experiment with something comparable in the browser.&lt;/p&gt;
&lt;h2&gt;Tink through Wasm&lt;/h2&gt;
&lt;p&gt;The Tink maintainers started to work on a Typescript version of Tink but as of today it is still experimental and there’s no timeline for a stable release. We therefore turned to Wasm to lift the stable Golang implementation of Tink to JavaScript.&lt;/p&gt;
&lt;p&gt;In general, there are two requirements to lift a program to Wasm:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Compiler support to translate the original application into Wasm bytecode&lt;/li&gt;
&lt;li&gt;A system interface to allow the Wasm bytecode to interact with the rest of the system (e.g., the filesystem or the network)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Wasm was introduced in 2017, and since then it has gained a lot of popularity and widespread support. When it comes to compiling from Go to Wasm, we have two options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Go compiler supports the generation of Wasm bytecode out of the box, although Go still doesn’t have native support for &lt;a href=&quot;https://wasi.dev/&quot;&gt;The WebAssembly System Interface&lt;/a&gt; (WASI)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tinygo.org/&quot;&gt;TinyGo&lt;/a&gt;, which offers support for WASI&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Unfortunately, TinyGo has limited support for the standard Go libraries and is not able to run Tink out of the box, so for this blogpost we’ll stick to the native Go compiler.&lt;/p&gt;
&lt;h3&gt;Lifting Tink to Wasm&lt;/h3&gt;
&lt;p&gt;To compile a Go program in Wasm with the native compiler, we simply need to set &lt;code&gt;GOOS&lt;/code&gt; and &lt;code&gt;GOARCH&lt;/code&gt; to &lt;code&gt;GOOS=js GOARCH=wasm&lt;/code&gt;.
Let’s try that out with Tink and see if it still passes the unit tests:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/google/tink
Cloning into &apos;tink&apos;...
$ cd tink/go
$ export PATH=&quot;$PATH:$(go env GOROOT)/misc/wasm&quot;
$ GOOS=js GOARCH=wasm  go test ./...
…
FAIL    github.com/google/tink/go/integration/hcvault [build failed]
…
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Hashicorp Vault tests fail and in general the tests are slower than native, but overall Tink seems to be usable. In particular, the test suites for signature, aed and core seem to pass.&lt;/p&gt;
&lt;h2&gt;A simple Tink interface&lt;/h2&gt;
&lt;p&gt;To use Tink from JavaScript we need to expose an interface usable from the browser. Let’s write a simple encrypt/decrypt pair that uses AES GCM to encrypt/decrypt data and returns it base64-encoded to the caller.&lt;/p&gt;
&lt;p&gt;The code uses &lt;a href=&quot;https://pkg.go.dev/syscall/js&quot;&gt;syscall/js&lt;/a&gt; to interact with JavaScript.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
func encryptString(this js.Value, args []js.Value) interface{} {

   var msg string
   if len(args) &amp;lt; 1 {
       fmt.Println(&quot;Not enough arguments&quot;)
       return nil
   }

   if args[0].Type() != js.TypeString {
       fmt.Println(&quot;the argument is not a string&quot;)
       return nil
   }

   msg = args[0].String()
   fmt.Println(&quot;Encrypting:&quot;, msg)
   //For this example we use AES_128_GCM. Let&apos;s create a keyset
   kh, err := keyset.NewHandle(aead.AES128GCMKeyTemplate())
   if err != nil {
       fmt.Printf(&quot;Error creating the keyset %v\n&quot;, err)
       return nil
   }

   aeadPrimitive, err := aead.New(kh)
   if err != nil {
       fmt.Printf(&quot;Error creating the aead primitive %v\n&quot;, err)
       return nil
   }

   //Let&apos;s encrypt the message
   ct, err := aeadPrimitive.Encrypt([]byte(msg), nil)
   if err != nil {
       fmt.Printf(&quot;Error encrypting data %v\n&quot;, err)
       return nil
   }

   b64EncryptedData := base64.URLEncoding.EncodeToString(ct)

   //we are returning the keyset to the caller which is not recommended behavior
   // so we need to export it through the insecurecleartextkeyset interface
   buffer := new(bytes.Buffer)
   exportedPriv := keyset.NewJSONWriter(buffer)
   err = insecurecleartextkeyset.Write(kh, exportedPriv)
   if err != nil {
       fmt.Printf(&quot;Error exporting keyset %v\n&quot;, err)
       return nil
   }

   b64Keyset := base64.URLEncoding.EncodeToString(buffer.Bytes())

   return []interface{}{b64EncryptedData, b64Keyset}
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function to decrypt a string given a key looks as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func decryptString(this js.Value, args []js.Value) interface{} {

   if len(args) &amp;lt; 2 {
       fmt.Println(&quot;Not enough arguments&quot;)
       return nil
   }

   if args[0].Type() != js.TypeString {
       fmt.Println(&quot;the value to decrypt is not a string&quot;)
       return nil
   }

   if args[1].Type() != js.TypeString {
       fmt.Println(&quot;the keyset is not a string&quot;)
       return nil
   }

   encryptedString := args[0].String()
   b64EncKeyset := args[1].String()

   encryptedStringBinary, err := base64.URLEncoding.DecodeString(encryptedString)
   if err != nil {
       fmt.Println(&quot;Error decoding Base64 encoded data&quot;)
       return nil
   }

   decodedKeyset, err := base64.URLEncoding.DecodeString(b64EncKeyset)
   if err != nil {
       fmt.Println(&quot;Error decoding Base64 encoded data&quot;)
       return nil
   }

   //let&apos;s load the keyset to decrypt encryptedString
   reader := keyset.NewJSONReader(bytes.NewBuffer([]byte(decodedKeyset)))
   decryptionKeyset, err := insecurecleartextkeyset.Read(reader)
   if err != nil {
       fmt.Printf(&quot;Error reading the keyset %v\n&quot;, err)
       return nil
   }

   aeadPrimitive, err := aead.New(decryptionKeyset)
   if err != nil {
       fmt.Printf(&quot;Error creating the aead primitive %v\n&quot;, err)
       return nil
   }

   //now we can decrypt the string
   decryptedString, err := aeadPrimitive.Decrypt([]byte(encryptedStringBinary), nil)
   if err != nil {
       fmt.Printf(&quot;Error decrypting data %v\n&quot;, err)
       return nil
   }

   b64DecryptedData := base64.URLEncoding.EncodeToString([]byte(decryptedString))
   return b64DecryptedData
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have the primitives to encrypt/decrypt data we can expose them to the browser&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func main() {

   global := js.Global()
   global.Set(&quot;wasmEncryptString&quot;, js.FuncOf(encryptString))
   global.Set(&quot;wasmDecryptString&quot;, js.FuncOf(decryptString))
   fmt.Println(&quot;wrappers ready&quot;)

   //never exit
   done := make(chan struct{}, 0)
   &amp;lt;-done
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the functions from the browser is straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WebAssembly.instantiateStreaming(fetch(&apos;main.wasm&apos;), go.importObject).then(
  async (result) =&amp;gt; {
    mod = result.module
    inst = result.instance

    go.run(inst)
    inst = await WebAssembly.instantiate(mod, go.importObject)

    ret = wasmEncryptString(&apos;aaa&apos;)
    console.log(ret)
    console.log(wasmDecryptString(ret[0], ret[1]))
  }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Benchmarking and optimization&lt;/h2&gt;
&lt;p&gt;This all seems great except that when we compile the Wasm module, we get:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;% GOOS=js GOARCH=wasm go build -o main.wasm .
% du -h main.wasm
7.8M	main.wasm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Almost 8 MB! That really wouldn’t make for a snappy user experience.
Can we do better? Stripping debugging symbols only reduces the file size by about 300 KB. However, we can compress the file with &lt;a href=&quot;https://en.wikipedia.org/wiki/Brotli&quot;&gt;brotli&lt;/a&gt; and get a more manageable 2.1 MB file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;% GOOS=js GOARCH=wasm go build -o main.wasm main.go
% du -h main.wasm
7.8M    main.wasm
% brotli main.wasm -o compressed.wasm.br
% du -h compressed.wasm.br
2.1M    compressed.wasm.br
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the use case, we could further prune Tink to remove all the modules that are not needed by our Wasm module.&lt;/p&gt;
&lt;p&gt;To benchmark Tink’s runtime performance against native Web Crypto, we implemented a comparable encrypt/decrypt pair as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function encryptString(msg) {
  const key = await window.crypto.subtle.generateKey(
    { name: &apos;AES-GCM&apos;, length: 128 },
    true, // extractable
    [&apos;encrypt&apos;, &apos;decrypt&apos;]
  )
  let iv = new Uint8Array(12)
  window.crypto.getRandomValues(iv)
  const encrypted = await window.crypto.subtle.encrypt(
    { name: &apos;AES-GCM&apos;, iv: iv },
    key,
    new TextEncoder().encode(msg)
  )
  const exportedKey = await window.crypto.subtle.exportKey(&apos;jwk&apos;, key)

  return [encrypted, exportedKey, iv]
}

async function decryptString(msg, keyExport, iv) {
  const key = await window.crypto.subtle.importKey(
    &apos;jwk&apos;,
    keyExport,
    { name: &apos;AES-GCM&apos;, length: 128 },
    true, // extractable
    [&apos;encrypt&apos;, &apos;decrypt&apos;]
  )

  const decrypted = await window.crypto.subtle.decrypt(
    { name: &apos;AES-GCM&apos;, iv: iv },
    key,
    msg
  )

  const decoded = new window.TextDecoder().decode(new Uint8Array(decrypted))

  return decoded
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we ran both function pairs with 100 times as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (var i = 0; i &amp;lt; 100; i++) {
  let val = makeid(i + 1)
  let t0 = performance.now()
  ret = wasmEncryptString(val)
  decrypted = wasmDecryptString(ret[0], ret[1])
  let t1 = performance.now()
  if (val != decrypted) {
    console.log(`invalid result: encrypted ${val} - received ${decrypted}`)
    break
  }
  console.log(
    `Call to wasmEncryptString/wasmDecryptString took ${t1 - t0} milliseconds.`
  )
}

for (var i = 0; i &amp;lt; 100; i++) {
  let val = makeid(i + 1)
  let t0 = performance.now()
  ret = await encryptString(val)
  decrypted = await decryptString(ret[0], ret[1], ret[2])
  let t1 = performance.now()
  if (val != decrypted) {
    console.log(`invalid result: encrypted ${val} - received ${decrypted}`)
    break
  }
  console.log(
    `Call to encryptString/decryptString took ${t1 - t0} milliseconds.`
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Excluding the first call to wasmEncryptString/wasmDecryptString which each took approximately 1.2 ms across 10 runs, the average for the Tink pair was 0.2-0.3 ms compared to WebCrypto’s 0.1-0.2 ms. While Tink is measurably slower than native, we believe it is negligible.&lt;/p&gt;
&lt;h2&gt;Accessing a Hardware Security Module directly from the browser&lt;/h2&gt;
&lt;p&gt;Lifting Tink to Wasm allows us to do some pretty exciting things, and one of them is to encrypt data using &lt;a href=&quot;https://cloud.google.com/kms/docs/envelope-encryption&quot;&gt;Envelope Encryption&lt;/a&gt; with a master key stored in a secure HSM. In envelope encryption, the HSM key acts as a key encryption key (KEK). That is, it&apos;s used to encrypt data encryption keys (DEK) which in turn are used to encrypt actual data.&lt;/p&gt;
&lt;p&gt;For this blogpost we use a Cloud KMS, but the same principles are applicable to any HSM that has an adapter for Tink. While both GCP and AWS offer KMS solutions, Google doesn’t expose &apos;access-control-allow-origin&apos; on GCP APIs, so calls to GCP KMS from the browser are blocked by Cross-origin resource sharing (CORS) policies; therefore, we can only use the AWS KMS from the browser.&lt;/p&gt;
&lt;p&gt;After creating a KEK in KMS, to encrypt each message you must:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate a data encryption key (DEK) locally.&lt;/li&gt;
&lt;li&gt;Use the DEK locally to encrypt the message.&lt;/li&gt;
&lt;li&gt;Use Cloud KMS to encrypt (wrap) the DEK with the KEK.&lt;/li&gt;
&lt;li&gt;Store the encrypted data and the wrapped DEK.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You don&apos;t need to implement this envelope encryption process from scratch when you use Tink.&lt;/p&gt;
&lt;p&gt;To use Tink for envelope encryption, you provide Tink with a key URI and credentials. The key URI points to your KEK in KMS, and the credentials let Tink use the KEK. Tink generates the DEK, encrypts the data, wraps the DEK, and then returns a single ciphertext with the encrypted data and wrapped DEK.&lt;/p&gt;
&lt;p&gt;For the sake of brevity we’ll skip the details on key creation in KMS and authentication to the user AWS account. We’ll also skip the details on how to parse arguments from JavaScript - for details, please see the section below on how to use &lt;code&gt;fetch&lt;/code&gt; from Go in Wasm.&lt;/p&gt;
&lt;p&gt;The first step is to create a KMS instance:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func constructAWSKMS(keyURI string, data map[string]string) (*kms.KMS, error) {
   crVal := credentials.Value{
       AccessKeyID:     data[&quot;access_key&quot;],
       SecretAccessKey: data[&quot;secret&quot;],
   }
   creds := credentials.NewStaticCredentialsFromCreds(crVal)

   region, err := getRegion(keyURI)
   if err != nil {
       return nil, err
   }

   session := session.Must(session.NewSession(&amp;amp;aws.Config{
       Credentials: creds,
       Region:      aws.String(region),
   }))

   return kms.New(session), nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have a KMS instance we can create a &lt;a href=&quot;https://pkg.go.dev/github.com/google/tink/go/core/registry&quot;&gt;&lt;code&gt;registry.KMSClient&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kms, err := constructAWSKMS(keyURI, creds)

if err != nil {
    fmt.Printf(&quot;Error constructing kms %v\n&quot;, err)
    return js.Value{}, errors.New(&quot;Error constructing kms&quot;)
}
KMSClient, err = awskms.NewClientWithKMS(keyURI, kms)

if err != nil {
    fmt.Printf(&quot;Error connecting to aws kms %v\n&quot;, err)
    return js.Value{}, errors.New(&quot;Error connecting to aws kms&quot;)
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;KMSClient&lt;/code&gt; allows us to get an AEAD primitive from KMS and perform envelope encryption/decryption with it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   registry.RegisterKMSClient(KMSClient)
   masterKey, err := KMSClient.GetAEAD(keyURI)
   if err != nil {
       fmt.Printf(&quot;Unable to get the master key from KMS %v\n&quot;, err)
       return nil
   }

   //create a keyset encrypted with the master key from KMS
   dek := aead.AES128CTRHMACSHA256KeyTemplate()
   kh, err := keyset.NewHandle(aead.KMSEnvelopeAEADKeyTemplate(keyURI, dek))
   if err != nil {
       fmt.Printf(&quot;Unable to create a keyset %v\n&quot;, err)
       return nil
    }
   aeadPrimitive, err := aead.New(kh)
   if err != nil {
       fmt.Printf(&quot;Unable to create the aead primitive %v\n&quot;, err)
       return nil
   }

   //Let&apos;s encrypt the message
   ct, err := aeadPrimitive.Encrypt([]byte(msg), nil)
   if err != nil {
       fmt.Printf(&quot;Unable to encrypt the message %v\n&quot;, err)
       return nil
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To decrypt a ciphertext we first load the key and decrypt it with the master key from KMS and then we can decrypt the message as we would normally do&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
func decryptStringWithKMS(data, encryptedKeyset, keyURI string, kmsClient registry.KMSClient) (string, error) {

   fmt.Println(&quot;Decrypting:&quot;, data)

   registry.RegisterKMSClient(kmsClient)
   masterKey, err := kmsClient.GetAEAD(keyURI)
   if err != nil {
       return &quot;&quot;, err
   }

   encryptedStringBinary, err := base64.URLEncoding.DecodeString(data)
   if err != nil {
       return &quot;&quot;, errors.New(&quot;Error decoding Base64 encoded data&quot;)
   }

   decodedKeyset, err := base64.URLEncoding.DecodeString(encryptedKeyset)
   if err != nil {
       return &quot;&quot;, errors.New(&quot;Error decoding Base64 encoded data&quot;)
   }

   reader := keyset.NewJSONReader(strings.NewReader(string(decodedKeyset)))

   // Read reads the encrypted keyset handle back from the io.Reader
   // implementation and decrypts it using the master key.
   kh, err := keyset.Read(reader, masterKey)
   if err != nil {
       return &quot;&quot;, err
   }

   a, err := aead.New(kh)
   if err != nil {
       return &quot;&quot;, err
   }

   //now we can decrypt the string
   decryptedString, err := a.Decrypt([]byte(encryptedStringBinary), nil)
   if err != nil {
       return &quot;&quot;, err
   }
   return string(decryptedString), nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Putting this all together from JavaScript:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WebAssembly.instantiateStreaming(fetch(&quot;main.wasm&quot;), go.importObject).then(async (result) =&amp;gt; {
    mod = result.module;
    inst = result.instance;

    go.run(inst);
    inst = await WebAssembly.instantiate(mod, go.importObject);

    credsAWS = {
        &apos;access_key&apos;: &quot;YOUR_ACCESS_KEY&quot;,
        &apos;secret&apos;: &quot;YOUR_SECRET&quot;,
    }

    ret = await wasmEncryptStringKMS(&quot;test hsm&quot;, &quot;aws-kms://arn:aws:kms:us-east-1:489321446924:key/cdb44898-ce62-4799-b743-bf39631c9b51
    &quot;, &quot;aws&quot;, credsAWS)
    console.log(ret)
    ret = await wasmDecryptStringKMS(ret[0], ret[1], &quot;aws-kms://arn:aws:kms:us-east-1:489321446924:key/cdb44898-ce62-4799-b743-bf39631c9b51&quot;, &quot;aws&quot;, credsAWS)
    console.log(ret)
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows us to store the encryption key securely without having to worry about leaking key material.&lt;/p&gt;
&lt;h3&gt;Notes on making network requests from Wasm&lt;/h3&gt;
&lt;p&gt;Quoting from the &lt;code&gt;FuncOf&lt;/code&gt; reference:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Invoking the wrapped Go function from JavaScript will pause the event loop and spawn a new goroutine. Other wrapped functions which are triggered during a call from Go to JavaScript get executed on the same goroutine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;As a consequence, if one wrapped function blocks, JavaScript&apos;s event loop is blocked until that function returns. Hence, calling any async JavaScript API, which requires the event loop, like fetch (http.Client), will cause an immediate deadlock. Therefore a blocking function should explicitly start a new goroutine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Go HTTP calls use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;&lt;code&gt;fetch&lt;/code&gt;&lt;/a&gt; in Javascript. This results in a blocking operation (network call), which needs to be handled properly otherwise it will result in a deadlock. In our specific example, &lt;code&gt;fetch&lt;/code&gt; is used to encrypt the message with the key stored in KMS.&lt;/p&gt;
&lt;p&gt;To solve the issue, blocking functions should explicitly start a new goroutine. However, this makes returning data to Javascript trickier because we can’t use channels (we’d be back in the deadlock). The easiest solution is to create a Promise through &lt;code&gt;syscall/js&lt;/code&gt; and call &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve&quot;&gt;&lt;code&gt;resolve()&lt;/code&gt;&lt;/a&gt; through the goroutine:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func resolvePromise(cb func() (js.Value, error)) js.Value {
   promiseHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
       resolve := args[0]
       reject := args[1]

       go func() {
           ret, err := cb() //execute the blocking function in a goroutine
           if err != nil {
               reject.Invoke(js.Global().Get(&quot;Error&quot;).Error(err))
               return
           }

           resolve.Invoke(ret)
       }()

       return nil
   })
   defer promiseHandler.Release()

   return js.Global().Get(&quot;Promise&quot;).New(promiseHandler)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this blogpost we’ve demonstrated an example where Tink makes it very easy to perform envelope encryption and to interface with a Cloud KMS, which would be significantly harder to implement with WebCrypto. While we used AWS HSMs for this blogpost, it is equally easy to implement the same mechanism with any HSM (including portable ones such as &lt;a href=&quot;https://www.yubico.com/product/yubihsm-2/&quot;&gt;YubiHSM&lt;/a&gt;) by creating a Tink integration for it.&lt;/p&gt;
&lt;p&gt;The Tink team is working on a TypeScript port of the library and we are eagerly waiting for its release. While lifting Tink to Wasm is definitely possible, the tradeoffs of using Tink via Wasm vs native WebCrypto are not obvious and are highly dependent on your project requirement.&lt;/p&gt;
&lt;p&gt;In a future blogpost we’ll expand further on client-side encryption to show how we can allow the user to store encrypted data without the significant burden of managing keys or credentials.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Phishing Attacks – WebAuthn to the rescue</title><link>https://slashid.dev/blog/webauthn-antiphishing/</link><guid isPermaLink="true">https://slashid.dev/blog/webauthn-antiphishing/</guid><description>Authentication token theft is on the rise, with the latest Uber breach demonstrating yet again the threat that it poses. WebAuthn significantly reduces user experience friction and hence allows for more frequent authentication prompts, offsetting the need for long-lived tokens and significantly curbing the risk of phishing.</description><pubDate>Mon, 12 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;What are Authentication Tokens&lt;/h2&gt;
&lt;p&gt;Tokens are the most common way to hold user authentication information.&lt;/p&gt;
&lt;p&gt;A token can be anything from a random string of characters to a fully formed JSON Web Token (JWT). Their primary purpose is to prove that the user navigating to a particular site has previously verified their identity at some point. This is generally achieved either by verifying the signature on the token or by pinging the identity provider to confirm the validity and authenticity of a token.&lt;/p&gt;
&lt;p&gt;Tokens typically have an expiration date set by the server that issued them. Usually, the expiration date of tokens created for B2C purposes is 30 to 90 days, while in enterprise settings it can be anywhere between a few hours to several weeks.&lt;/p&gt;
&lt;h2&gt;Why are Authentication Tokens valuable?&lt;/h2&gt;
&lt;p&gt;Attackers who obtain authentication tokens can impersonate a user on a website or application. Once they exploit tokens to breach the system, they can move laterally to compromise the rest of the infrastructure in enterprise scenarios, or commit various forms of theft and fraud in the consumer world.&lt;/p&gt;
&lt;p&gt;Before the introduction of multifactor authentication (MFA), credential stuffing attacks were significantly more frequent than token-stealing attacks, since it is generally easier to guess a password than to compromise a website, a browser, or a device.&lt;/p&gt;
&lt;p&gt;However, today botnets and cybercriminals increasingly target cookies since they allow MFA bypass without resorting to social engineering.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.f5.com/labs/articles/threat-intelligence/genesis-marketplace--a-digital-fingerprint-darknet-store&quot;&gt;Genesis&lt;/a&gt; marketplace, one of the biggest darknet markets used by attackers, contains hundreds of thousands of stolen cookies, including the one used to initiate the recent attack against the game developer Electronic Arts which resulted in 780GB of proprietary data &lt;a href=&quot;https://www.vice.com/en/article/7kvkqb/how-ea-games-was-hacked-slack&quot;&gt;exfiltrated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other examples of recent attacks involving compromised cookies/tokens include the last generation of the &lt;a href=&quot;https://news.sophos.com/en-us/2022/08/18/cookie-stealing-the-new-perimeter-bypass/&quot;&gt;EMOTET malware&lt;/a&gt;, possibly the most dangerous botnet of the last decade, and financially motivated phishing campaigns against popular &lt;a href=&quot;https://blog.google/threat-analysis-group/phishing-campaign-targets-youtube-creators-cookie-theft-malware/&quot;&gt;YouTube influencers&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;How are Auth Tokens stolen?&lt;/h2&gt;
&lt;p&gt;Auth Tokens can be stolen through a variety of methods, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Social Engineering&lt;/strong&gt;: In this scenario, the attacker creates a phishing campaign where the victim successfully logs into a remote service but their credentials are intercepted. Microsoft calls these attacks &lt;a href=&quot;https://www.microsoft.com/security/blog/2022/07/12/from-cookie-theft-to-bec-attackers-use-aitm-phishing-sites-as-entry-point-to-further-financial-fraud/&quot;&gt;&apos;Adversary in the middle&apos; (AiTM)&lt;/a&gt;. Frameworks to carry out credential phishing attacks are readily available on &lt;a href=&quot;https://github.com/kgretzky/evilginx2&quot;&gt;GitHub&lt;/a&gt; and in various &lt;a href=&quot;https://twitter.com/jeremy_kirk/status/1566736491471966208?s=51&amp;amp;t=wbRqIqSnt_4gygOsOkZbxw&quot;&gt;forums&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Man in the middle (MITM) attacks&lt;/strong&gt;: If the server doesn’t employ https or if an attacker can forge a SSL certificate, they can intercept the victim&apos;s network traffic and steal cookies/tokens.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Web Vulnerability (XSS, Session Hijacking)&lt;/strong&gt;: Non-HttpOnly cookies are at risk of being stolen through various &lt;a href=&quot;https://owasp.org/www-community/attacks/Session_hijacking_attack&quot;&gt;web exploitation techniques&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Browser Compromise&lt;/strong&gt;: As discussed previously, any violation of the browser security &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/compromised-renderers.md&quot;&gt;model&lt;/a&gt; can result in stolen cookies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Device Compromise&lt;/strong&gt;: Countless malware and botnets are capable of stealing cookies once a user device is compromised. Notable examples include: &lt;a href=&quot;https://www.zscaler.com/blogs/security-research/raccoon-stealer-v2-latest-generation-raccoon-family#:~:text=The%20Raccoon%20Malware%20is%20a,theft%20from%20all%20cryptocurrency%20wallets.&quot;&gt;Raccoon Stealer&lt;/a&gt;, &lt;a href=&quot;https://github.com/Alexuiop1337/SoranoStealer&quot;&gt;Sorano&lt;/a&gt; and &lt;a href=&quot;https://malpedia.caad.fkie.fraunhofer.de/details/win.redline_stealer&quot;&gt;RedLine&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most recently, more and more enterprise customers have been attacked with various forms of reverse proxies to bypass &lt;a href=&quot;https://twitter.com/CircleCI/status/1570821478295248896&quot;&gt;MFA&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once the attacker has stolen cookies/tokens, they generally breach and move laterally in the system to elevate their privileges within an enterprise setting or to identity theft or credit card information theft within a B2C scenario.&lt;/p&gt;
&lt;h2&gt;WebAuthn, in a nutshell&lt;/h2&gt;
&lt;p&gt;WebAuthn enables safe public-key cryptography inside the browser directly from JavaScript through an interface exposed via &lt;code&gt;navigator.credentials&lt;/code&gt;.
This is what happens under the hood when your user registers a new account on a website with WebAuthn:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The browser interacts with an Authenticator, which can either be a Platform Authenticator (such as Windows Hello, TouchID, FaceID and similar) or an external/Roaming Authenticator such as a Yubikey or a Titan Key.&lt;/li&gt;
&lt;li&gt;The Authenticator generates a key pair, stores the private key in a secure location and returns the public key to the server via the browser API.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Any time a user wants to log-in, they sign a blob with the private key stored on their device. Then, the remote server verifies the authenticity of the blob by checking its signature through the public key obtained in the registration process.&lt;/p&gt;
&lt;h2&gt;WebAuthn Anti-Phishing Properties&lt;/h2&gt;
&lt;p&gt;WebAuthn provides strong protection against phishing attacks. In fact, all 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 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;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. This is the only way in which they can bypass the WebAuthn checks.&lt;/p&gt;
&lt;p&gt;In either scenario, a successful attack still won&apos;t give the attacker 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;Most importantly, due to the origin checks, most of the recent attacks involving domain squatting and phishing would fall flat because the reverse proxy wouldn’t be able to initiate the WebAuthn authentication process.&lt;/p&gt;
&lt;p&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 vastly superior authentication method.&lt;/p&gt;
&lt;h2&gt;The technical details&lt;/h2&gt;
&lt;p&gt;For the technically curious, here’s a code snippet taken from the Chrome web browser. It shows the set of checks Chrome performs on a WebAuthn authentication assertion request to verify its origin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
 status = security_checker_-&amp;gt;ValidateDomainAndRelyingPartyID(
     caller_origin, options-&amp;gt;relying_party_id, request_type,
     options-&amp;gt;remote_desktop_client_override);
 if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
   CompleteGetAssertionRequest(status);
   return;
 }
…
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When an authentication request reaches the browser, Chrome will call &lt;code&gt;ValidateDomainAndRelyingPartyID&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;blink::mojom::AuthenticatorStatus
WebAuthRequestSecurityChecker::ValidateDomainAndRelyingPartyID(
   const url::Origin&amp;amp; caller_origin,
   const std::string&amp;amp; relying_party_id,
   RequestType request_type,
   const blink::mojom::RemoteDesktopClientOverridePtr&amp;amp;
       remote_desktop_client_override) {
 …

 blink::mojom::AuthenticatorStatus domain_validation =
     OriginAllowedToMakeWebAuthnRequests(caller_origin);
 if (domain_validation != blink::mojom::AuthenticatorStatus::SUCCESS) {
   return domain_validation;
 }

 …

 if (!OriginIsAllowedToClaimRelyingPartyId(relying_party_id,
                                           relying_party_origin)) {
   return blink::mojom::AuthenticatorStatus::BAD_RELYING_PARTY_ID;
 }
 return blink::mojom::AuthenticatorStatus::SUCCESS;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then it calls &lt;code&gt;OriginAllowedToMakeWebAuthnRequests&lt;/code&gt; to check whether the origin is localhost or https. It then proceeds to call &lt;code&gt;OriginIsAllowedToClaimRelyingPartyId&lt;/code&gt; to verify that the current domain matches the domain associated with the key, thus preventing phishing attempts where a domain other than the original tries to retrieve a valid user credential.
Let’s look at the two functions one by one.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;blink::mojom::AuthenticatorStatus OriginAllowedToMakeWebAuthnRequests(
   url::Origin caller_origin) {
 …

 if (caller_origin.opaque()) {
   return blink::mojom::AuthenticatorStatus::OPAQUE_DOMAIN;
 }

 // The scheme is required to be HTTP(S).  Given the
 // |network::IsUrlPotentiallyTrustworthy| check below, HTTP is effectively
 // restricted to just &quot;localhost&quot;.
 if (caller_origin.scheme() != url::kHttpScheme &amp;amp;&amp;amp;
     caller_origin.scheme() != url::kHttpsScheme) {
   return blink::mojom::AuthenticatorStatus::INVALID_PROTOCOL;
 }

 // TODO(https://crbug.com/1158302): Use IsOriginPotentiallyTrustworthy?
 if (url::HostIsIPAddress(caller_origin.host()) ||
     !network::IsUrlPotentiallyTrustworthy(caller_origin.GetURL())) {
   return blink::mojom::AuthenticatorStatus::INVALID_DOMAIN;
 }

 return blink::mojom::AuthenticatorStatus::SUCCESS;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;OriginAllowedToMakeWebAuthnRequests&lt;/code&gt; will only allow localhost or https origins.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Returns whether a caller origin is allowed to claim a given Relying Party ID.
// It&apos;s valid for the requested RP ID to be a registrable domain suffix of, or
// be equal to, the origin&apos;s effective domain.  Reference:
// https://html.spec.whatwg.org/multipage/origin.html#is-a-registrable-domain-suffix-of-or-is-equal-to.
bool OriginIsAllowedToClaimRelyingPartyId(
   const std::string&amp;amp; claimed_relying_party_id,
   const url::Origin&amp;amp; caller_origin) {
 // `OriginAllowedToMakeWebAuthnRequests()` must have been called before.
 DCHECK_EQ(OriginAllowedToMakeWebAuthnRequests(caller_origin),
           blink::mojom::AuthenticatorStatus::SUCCESS);

…

 if (claimed_relying_party_id.empty()) {
   return false;
 }

 if (caller_origin.host() == claimed_relying_party_id) {
   return true;
 }

 if (!caller_origin.DomainIs(claimed_relying_party_id)) {
   return false;
 }

…

 return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;OriginIsAllowedToClaimRelyingPartyId&lt;/code&gt; verifies that the request comes from the same domain associated with the relying party, which is stored in the authentication assertion request.
The function first checks whether the relying party ID in the WebAuthn request is empty; if so, the request is denied. If the relying party ID matches the domain where the request originates, or is a registrable domain &lt;a href=&quot;https://github.com/w3c/webauthn/issues/963&quot;&gt;suffix&lt;/a&gt;, the request is allowed.&lt;/p&gt;
&lt;p&gt;The enforcement of https, combined with the condition that the relying party ID has to match the origin domain of the request, can prevent reverse-proxy phishing attacks.&lt;/p&gt;
&lt;p&gt;For example, in the scenario illustrated below the attacker would need to craft a payload of this kind to call &lt;code&gt;navigation.credentials.get()&lt;/code&gt; and begin an authentication assertion process.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var options = {
 // The challenge is produced by the server; see the Security Considerations
 challenge: new Uint8Array(/* 32 more random bytes generated by the server */]),
 rpId: “target-website.com”
 timeout: 120000,  // 2 minutes
};

navigator.credentials.get({ &quot;publicKey&quot;: options })
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, because the &lt;code&gt;rpID&lt;/code&gt; and the origin do not match, &lt;code&gt;OriginIsAllowedToClaimRelyingPartyId&lt;/code&gt; would prevent the call from succeeding, thus preventing the attack.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/webauthn-antiphishing/evil-proxy.png&quot; alt=&quot;The flow of an attack using EvilProxy(Source: Resecurity)&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In summary, WebAuthn can significantly reduce the risk of phishing as well as the usefulness of a stolen session token.&lt;/p&gt;
&lt;p&gt;In fact, WebAuthn goes a step further than other authentication methods, and prevents phishing attacks while also reducing UX friction. The seamless user experience allows for more frequent authentication prompts, thus offsetting the need for long-lived authentication tokens.&lt;/p&gt;
&lt;p&gt;Like most security measures, Webauthn is not a panacea in and of itself. In particular, weak key recovery mechanisms and the presence of weaker authentication schemes(e.g., SMS OTP) could render WebAuthn moot.&lt;/p&gt;
&lt;p&gt;However, its adoption in conjunction with short-lived session cookies and other mitigations (e.g., risk-based MFA, IP address checks/geofencing, browser fingerprinting) can be an invaluable combination to curb phishing and eliminate credential stuffing attacks.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>Using Google Tink to sign JWTs with ECDSA</title><link>https://slashid.dev/blog/tink-jwt-ecdsa/</link><guid isPermaLink="true">https://slashid.dev/blog/tink-jwt-ecdsa/</guid><description>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.</description><pubDate>Mon, 20 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&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;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;/p&gt;
&lt;pre&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvZSBTbGFzaElEIiwiaWF0IjoxNTE2MjM5MDIyfQ.4cL42NsNCXLPEvmvNGxHN3wLuarpp98wwezHnSt2fqg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;has the following parts&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;table-wide&quot;&amp;gt;&lt;/p&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;{ &quot;alg&quot;: &quot;HS256&quot;, &quot;typ&quot;: &quot;JWT&quot;}&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;{&quot;sub&quot;: &quot;1234567890&quot;, &quot;name&quot;: &quot;SlashID User&quot;, &quot;iat&quot;: 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;p&gt;&amp;lt;/div&amp;gt;&lt;/p&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;/p&gt;
&lt;pre&gt;&lt;code&gt;signAndhash(base64UrlEncode(header) + &apos;.&apos; + base64UrlEncode(payload))
&lt;/code&gt;&lt;/pre&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=&quot;https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms&quot;&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=&quot;https://www.openbanking.org.uk/&quot;&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&apos;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=&quot;https://datatracker.ietf.org/doc/html/rfc6979&quot;&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=&quot;https://github.com/square/go-jose/issues/357&quot;&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=&quot;https://datatracker.ietf.org/doc/rfc8725/&quot;&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=&quot;https://pkg.go.dev/github.com/google/tink/go@v1.7.0&quot;&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=&quot;https://pkg.go.dev/github.com/google/tink/go@v1.7.0/jwt&quot;&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;img src=&quot;/blog/tink-jwt/jwt_lifecycle.jpg&quot; alt=&quot;SlashID Documentation Site&quot; /&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=&quot;https://www.rfc-editor.org/rfc/rfc7519&quot;&gt;JWT specification&lt;/a&gt;, and some custom claims:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   jwtID := uuid.New().String()

   now := time.Now()
   expiry := now.Add(tokenDuration)

   opts := &amp;amp;jwt.RawJWTOptions{
       Subject:      &amp;amp;userID,
       Issuer:       &amp;amp;tokenIssuer,
       JWTID:        &amp;amp;jwtID,
       IssuedAt:     &amp;amp;now,
       ExpiresAt:    &amp;amp;expiry,
       NotBefore:    &amp;amp;now,
       CustomClaims: claims,
   }

   rawJWT, err := jwt.NewRawJWT(opts)
   if err != nil {
       return &quot;&quot;, fmt.Errorf(&quot;failed to create new RawJWT: %w&quot;, err)
   }


&lt;/code&gt;&lt;/pre&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=&quot;https://github.com/google/tink/tree/master/go/jwt&quot;&gt;Tink jwt package&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;   signingKeyset, err := tm.keysetsRepo.GetKeyset()
   if err != nil {
       return &quot;&quot;, fmt.Errorf(&quot;failed to get token signing keyset: %w&quot;, err)
   }

   signer, err := jwt.NewSigner(signingKeyset)
   if err != nil {
       return &quot;&quot;, fmt.Errorf(&quot;failed to create new Signer: %w&quot;, err)
   }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To do this, we have introduced an interface, &lt;code&gt;KeysetsRepo&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type KeysetsRepo interface {
   GetKeyset() (*keyset.Handle, error)
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;   signedToken, err := signer.SignAndEncode(rawJWT)
   if err != nil {
       return &quot;&quot;, fmt.Errorf(&quot;failed to sign RawJWT: %w&quot;, err)
   }

   return signedToken, nil
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;   verificationKeyset, err := tm.keysetsRepo.GetPublicKeyset()
   if err != nil {
       return nil, fmt.Errorf(&quot;failed to get token verification keyset: %w&quot;, err)
   }

   verifier, err := jwt.NewVerifier(verificationKeyset)
   if err != nil {
       return nil, fmt.Errorf(&quot;failed to create new Verifier: %w&quot;, err)
   }
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;type KeysetsRepo interface {
   GetKeyset() (*keyset.Handle, error)
   GetPublicKeyset() (*keyset.Handle, error)
}
&lt;/code&gt;&lt;/pre&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=&quot;https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata&quot;&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;/p&gt;
&lt;pre&gt;&lt;code&gt;   opts := &amp;amp;jwt.ValidatorOpts{
       ExpectedIssuer:        &amp;amp;tokenIssuer,
       IgnoreTypeHeader:      true,
       IgnoreAudiences:       true,
       ExpectIssuedInThePast: true,
   }

   validator, err := jwt.NewValidator(opts)
   if err != nil {
       return nil, fmt.Errorf(&quot;failed to create new Validator: %w&quot;, err)
   }
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;   verifiedJWT, err := verifier.VerifyAndDecode(signedToken, validator)
   if err != nil {
       return nil, InvalidTokenError
   }

   return verifiedJWT, nil
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;type FSKeysetsRepo struct {
   keysetsFilePath string
   masterKey       tink.AEAD
}

func NewFSKeysetsRepo(keysetsFilePath string, masterKey tink.AEAD) *FSKeysetsRepo {
   return &amp;amp;FSKeysetsRepo{
       keysetsFilePath: keysetsFilePath,
       masterKey:       masterKey,
   }
}


func (r *FSKeysetsRepo) InitTokenKeyset() error {
   handle, err := keyset.NewHandle(jwt.ES256Template())
   if err != nil {
       return fmt.Errorf(&quot;failed to create new keyset handle with ES256 template: %w&quot;, err)
   }

   return r.writeKeysetToFile(handle)
}


func (r *FSKeysetsRepo) writeKeysetToFile(handle *keyset.Handle) error {
   f, err := os.Create(r.keysetsFilePath)
   if err != nil {
       return fmt.Errorf(&quot;failed to create keysets file %s: %w&quot;, r.keysetsFilePath, err)
   }
   defer f.Close() // unhandled error

   jsonWriter := keyset.NewJSONWriter(f)

   err = handle.Write(jsonWriter, r.masterKey)
   if err != nil {
       return fmt.Errorf(&quot;failed to write keyset to file %s: %w&quot;, r.keysetsFilePath, err)
   }

   return nil
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;   jsonWriter := keyset.NewJSONWriter(f)

   err = handle.Write(jsonWriter, r.masterKey)
   if err != nil {
       return fmt.Errorf(&quot;failed to write keyset to file %s: %w&quot;, r.keysetsFilePath, err)
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;func (r *FSKeysetsRepo) GetKeyset() (*keyset.Handle, error) {
   return r.readKeysetFromFile()
}

func (r *FSKeysetsRepo) readKeysetFromFile() (*keyset.Handle, error) {
   f, err := os.Open(r.keysetsFilePath)
   if err != nil {
       return nil, fmt.Errorf(&quot;failed to open keysets file %s: %w&quot;, r.keysetsFilePath, err)
   }
   defer f.Close() // unhandled error

   jsonReader := keyset.NewJSONReader(f)

   handle, err := keyset.Read(jsonReader, r.masterKey)
   if err != nil {
       return nil, fmt.Errorf(&quot;failed to read keyset as JSON: %w&quot;, err)
   }

   return handle, err
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;func (r *FSKeysetsRepo) GetPublicKeyset() (*keyset.Handle, error) {
   handle, err := r.readKeysetFromFile()
   if err != nil {
       return nil, err
   }

   publicHandle, err := handle.Public()
   if err != nil {
       return nil, fmt.Errorf(&quot;failed to get public keyset: %w&quot;, err)
   }

   return publicHandle, nil
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;func StartServer(h *APIHandler) error {
   mux := http.NewServeMux()
   mux.HandleFunc(&quot;/tokens&quot;, h.PostToken)
   mux.HandleFunc(&quot;/tokens/verify&quot;, h.PostVerifyToken)

   return http.ListenAndServe(&quot;:8080&quot;, mux)
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;func main() {
   conf := getConfigFromEnv()

   keysetsRepo := NewFSKeysetsRepo(
       conf.keysetsFilePath,
       masterKey,
   )

   tokenManager := NewTinkTokenManager(keysetsRepo)

   apiHandler := NewAPIHandler(tokenManager)

   err := keysetsRepo.InitTokenKeyset()
   if err != nil {
       log.Fatalf(&quot;Failed to initialize token keysets&quot;)
   }

   err = StartServer(apiHandler)
   if err != nil {
       log.Fatalf(&quot;Server encountered an error: %s&quot;, err.Error())
   }
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code&gt;func getMasterKey(conf *config) tink.AEAD {
   gcpClient, err := gcpkms.NewClientWithOptions(context.Background(), conf.keyURIPrefix)
   if err != nil {
       log.Fatal(err)
   }
   registry.RegisterKMSClient(gcpClient)

   masterKey, err := gcpClient.GetAEAD(conf.masterKeyURI)
   if err != nil {
       log.Fatalf(&quot;Failed to retrieve master key: %s&quot;, err.Error())
   }

   return masterKey
}
&lt;/code&gt;&lt;/pre&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;
</content:encoded><author>Joseph Gardner, Vincenzo Iozzo</author></item><item><title>The Security and Regulatory Compliance Benefits of WebAuthn</title><link>https://slashid.dev/blog/webauthn-compliance/</link><guid isPermaLink="true">https://slashid.dev/blog/webauthn-compliance/</guid><description>The WebAuthn standard helps you stop phishing and account takeover (ATO) attacks while maintaining HIPAA and SCA compliance. WebAuthn is significantly safer than passwords, due to the way the keys are stored and because it prevents credential stuffing and reuse attacks.</description><pubDate>Wed, 14 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;WebAuthn is a standard first published by the W3C in 2016. Its primary goal is a safe user-authentication method for web applications using public-key cryptography.&lt;/p&gt;
&lt;p&gt;Most browsers (the estimated coverage at the time of writing is ~&lt;a href=&quot;https://caniuse.com/?search=webauthn&quot;&gt;93%&lt;/a&gt;) support WebAuthn, however it has not yet been widely adopted by websites.&lt;/p&gt;
&lt;p&gt;While there are a number of adoption challenges that we will discuss in a separate post, this article will focus on the significant security and compliance advantages that WebAuthn provides.&lt;/p&gt;
&lt;h2&gt;WebAuthn, in a nutshell&lt;/h2&gt;
&lt;p&gt;WebAuthn enables safe public-key cryptography inside the browser directly from JavaScript through an interface exposed via navigator.credentials.
This is what happens under the hood when your user registers a new account on a website with WebAuthn:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The browser interacts with an Authenticator, which can either be a Platform Authenticator (such as Windows Hello, TouchID, FaceID and similar) or an external/Roamining Authenticator such as a Yubikey or a Titan Key.&lt;/li&gt;
&lt;li&gt;The Authenticator generates a key pair, stores the private key in a secure location and returns the public key to the server via the browser API.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Any time a user wants to log-in, they sign a blob with their private key stored on their device and the remote server verifies the authenticity of the blob by checking its signature through the public key obtained in the registration process.&lt;/p&gt;
&lt;h2&gt;HIPAA and PSD2 authentication requirements&lt;/h2&gt;
&lt;p&gt;Both Healthtech and fintech are booming yet highly-regulated fields. Both sectors must comply with strict information security standards across the world. Two of the better known standards are the Health Insurance Portability and Accountability Act (HIPAA), regulating the security of Personal Health Information (PHI) in US healthcare sector, and the Payment Services Directive (PSD2) for EU financial services, both of which mandate a strong authentication policy.&lt;/p&gt;
&lt;p&gt;It is beyond this article&apos;s purview to describe who must comply with these regulations or their broader scope, so in this section we will simply focus on the authentication requirements that both laws mandate.&lt;/p&gt;
&lt;p&gt;HIPAA mandates service providers to: &lt;em&gt;“Implement procedures to verify that a person or entity seeking access to electronic protected health information is the one claimed”&lt;/em&gt; (Security Standard &lt;a href=&quot;https://www.law.cornell.edu/cfr/text/45/164.312&quot;&gt;§164.312(d)&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;The US Department of Health and Human Services published additional verification standard &lt;a href=&quot;https://www.hhs.gov/sites/default/files/ocr/privacy/hipaa/administrative/securityrule/techsafeguards.pdf&quot;&gt;guidance&lt;/a&gt;, stating that an user can be verified in one of three ways, by requiring:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Something known only to that individual, such as a password or PIN.&lt;/li&gt;
&lt;li&gt;Something that an individual possesses, such as a smart card, a token, or a key.&lt;/li&gt;
&lt;li&gt;Something unique to the individual such as a biometric. Examples of biometrics include fingerprints, voice patterns, facial patterns or iris patterns.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By design, WebAuthn satisfies two out of the above three requirements out of the box. In fact, WebAuthn requires a device authenticator (e.g.,: FaceID, Windows Hello, TouchID) or a security key because that is where the private portion of the key as well as the ability to unlock that key which depending on the device is guarded either by a password/PIN or by biometrics.&lt;/p&gt;
&lt;p&gt;WebAuthn is the perfect authentication method to satisfy HIPAA requirements, not only because it covers two out of three requirements but also because it makes credentials unfishable further increasing the security posture of the user.&lt;/p&gt;
&lt;p&gt;PSD2 requires covered entities to protect their users through &lt;a href=&quot;https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=uriserv%3AOJ.L_.2018.069.01.0023.01.ENG&amp;amp;toc=OJ%3AL%3A2018%3A069%3ATOC&quot;&gt;Strong Customer Authentication (SCA)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;PSD2 states:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Where payment service providers apply strong customer authentication in accordance with Article 97(1) of Directive (EU) 2015/2366, &lt;strong&gt;the authentication shall be based on two or more elements which are categorized as knowledge, possession and inherence and shall result in the generation of an authentication code&lt;/strong&gt;.&lt;/em&gt;
&lt;em&gt;In order to ensure the application of strong customer authentication, it is also necessary to require adequate security features for the elements of strong customer authentication categorized as ‘knowledge’ (something only the user knows), such as length or complexity, for the elements categorized as ‘possession’ (something only the user possesses), such as algorithm specifications, key length and information entropy, and for the devices and software that read elements categorized as ‘inherence’ (something the user is) such as algorithm specifications, biometric sensor and template protection features, in particular to mitigate the risk that those elements are uncovered, disclosed to and used by unauthorised parties. &lt;strong&gt;It is also necessary to lay down the requirements to ensure that those elements are independent, so that the breach of one does not compromise the reliability of the others&lt;/strong&gt;, in particular when any of these elements are used through a multi-purpose device, namely a device such as a tablet or a mobile phone which can be used both for giving the instruction to make the payment and in the authentication process.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So how does WebAuthn help with PSD2-compliance?
The most clear-cut answer is that WebAuthn using an external key such as a Yubikey or a Titan key covers two out of the three PSD2 requirements, depending on the authenticator: either possession and inherence or possession and knowledge.&lt;/p&gt;
&lt;p&gt;The European Banking Authority is responsible for promulgating the RTS, the technical standards that govern SCA. However, individual member states are responsible for the laws and regulations that implement RTS. So, from a practical perspective, individual countries’ data regulators can and do alter their advised standards, and so each individual EU country might take a slightly different view on WebAuthn with a platform authenticator.&lt;/p&gt;
&lt;p&gt;On the one hand, modern devices store WebAuthn private keys in a separate TPM (Trusted Platform Module) that remains uncompromised even when the phone itself is. On the other hand, compromising the phone can still lead to social engineering attacks that trick the user into authorizing WebAuthn transactions.&lt;/p&gt;
&lt;p&gt;However, compromising a device will still lead to the compromise of any knowledge factor (e.g., a password). As such, we believe that WebAuthn with a platform authenticator on modern devices offers as much breach-independence as any other knowledge factor and can likely be used as the sole authentication mechanism for satisfying SCA requirements.&lt;/p&gt;
&lt;h2&gt;WebAuthn Anti-Phishing Properties&lt;/h2&gt;
&lt;p&gt;WebAuthn provides strong protection against phishing attacks. All compliant browsers enforce a number of security checks.&lt;/p&gt;
&lt;p&gt;First of all, WebAuthn doesn’t work without TLS. Further, browsers enforce origin checks and most browsers will prevent access to the platform authenticator unless the window is in focus or, in the case of Safari/Apple devices, the user triggers an action.&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 which is a very high barrier to overcome. This is the only way in which they can bypass the WebAuthn checks or perform a universal cross-site scripting (UXSS) attack.&lt;/p&gt;
&lt;p&gt;In either scenario, a successful attack still won&apos;t give the attacker access to the private key itself, but instead only a session token/cookie which will expire once the browsing session is over.&lt;/p&gt;
&lt;p&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 vastly superior authentication method.&lt;/p&gt;
&lt;h2&gt;Key Storage and Anti-Credential Stuffing&lt;/h2&gt;
&lt;p&gt;As hinted above, one key characteristic of WebAuthn in modern devices is that private keys never leave the secure storage they are created in. How this happens is implementation-dependent, but the guarantee is that, barring a compromise of the secure element itself and not solely of the operating system, the private key cannot be extracted.&lt;/p&gt;
&lt;p&gt;The bar for compromising a secure element is extremely high. For example, the secure element inside iOS devices has had only two public bugs since it was created.&lt;/p&gt;
&lt;p&gt;For instance, WebAuthn keys on iOS are stored in a system called &lt;a href=&quot;https://support.apple.com/en-gb/guide/security/sec59b0b31ff/web#:~:text=The%20Secure%20Enclave%20is%20a%20dedicated%20secure%20subsystem%20integrated%20into,Application%20Processor%20kernel%20becomes%20compromised.&quot;&gt;Secure Enclave&lt;/a&gt;, which is separate from the main processor and which is capable of preserving the integrity of the data even in the event of a device compromise. The Secure Enclave is the same system that stores Apple Pay and FaceID data.&lt;/p&gt;
&lt;p&gt;Further, unlocking access to a WebAuthn credential requires the user to confirm their identity either through FaceID or TouchID (depending on the device model). In both cases, the communication between the biometric device and the Secure Enclave happens over a physical serial peripheral bus over an encrypted &lt;a href=&quot;https://support.apple.com/en-gb/guide/security/sec067eb0c9e/1/web/1&quot;&gt;channel&lt;/a&gt;, and the data never leaves the Secure Enclave, where it is verified against a 2D representation of the original biometric data.&lt;/p&gt;
&lt;p&gt;While the example above specifically discusses Apple devices, similar security guarantees are provided by other Authenticators (whether platform or roaming), which make WebAuthn key extraction and, hence, credential stuffing attacks exceedingly difficult and unlikely, even in the event of full device compromise.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;WebAuthn easily allows companies to comply with many regulatory frameworks such as HIPAA and PSD2 without relying on insecure passwords or requiring too many authentication factors from the user.&lt;/p&gt;
&lt;p&gt;Additionally, it is clear that WebAuthn is a game-changer in authentication and it is by far the most secure authentication method available today. By employing WebAuthn, we can almost entirely remove credential stuffing, account takeover (ATO) and phishing attacks.&lt;/p&gt;
</content:encoded><author>Vincenzo Iozzo, SlashID Team</author></item><item><title>SlashID Analytics Webhooks</title><link>https://slashid.dev/blog/webhooks/</link><guid isPermaLink="true">https://slashid.dev/blog/webhooks/</guid><description>We are excited to release SlashID analytics and webhooks, providing greater visibility and actionable insights into your authentication flows.</description><pubDate>Sat, 10 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We are excited to release SlashID analytics and webhooks, providing greater visibility and actionable insights into your authentication flows. In this blog post, we will showcase our new features and how you can use them by answering four questions: why, what, how, and when.&lt;/p&gt;
&lt;h2&gt;The Why&lt;/h2&gt;
&lt;p&gt;If you’re reading this, there’s a good chance that authentication is a core step of your business logic, whether you’re selling products or controlling access to sensitive information and systems.&lt;/p&gt;
&lt;p&gt;Having deep and instant visibility on which users login at the time they login can make a great difference to your business, and you may wish to take specific actions in response to specific user behaviors.&lt;/p&gt;
&lt;p&gt;SlashID analytics does just this: with first-party visibility into your authentication flows, you can get crucial insights into the experience of your user base on your platform.&lt;/p&gt;
&lt;p&gt;These are just a few example use cases for SlashID analytics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Monitor authentication failures to redirect users to appropriate support channels&lt;/li&gt;
&lt;li&gt;Welcome new users with personalized messages and flows when they register for the first time&lt;/li&gt;
&lt;li&gt;Keep audit trails for compliance purposes&lt;/li&gt;
&lt;li&gt;Monitor the success of marketing campaigns that use &lt;a href=&quot;https://developer.slashid.dev/docs/api/post-persons-person-id-direct-id&quot;&gt;DirectID&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are many more, and as a result we have designed our analytics to be flexible, while maintaining a straightforward developer experience.&lt;/p&gt;
&lt;p&gt;Our analytics events include a correlation ID that you can use to tie together events from a single browser session, to follow each step of your users’ journey through your authentication and onboarding flows.&lt;/p&gt;
&lt;h2&gt;The What: SlashID Events&lt;/h2&gt;
&lt;p&gt;SlashID exposes analytics information through events – simple messages telling you that something happened. For example, the &lt;code&gt;AuthenticationSucceeded&lt;/code&gt; event indicates that a user successfully logged in to your application.&lt;/p&gt;
&lt;p&gt;All of our events are defined using &lt;a href=&quot;https://protobuf.dev/programming-guides/proto3/&quot;&gt;protobuf&lt;/a&gt;, which we publish in our &lt;a href=&quot;https://developer.slashid.dev/docs/access/concepts/events/event_definitions&quot;&gt;documentation&lt;/a&gt;. You can download these definitions and use them to generate code for handling events, or simply use them as reference.&lt;/p&gt;
&lt;p&gt;Our initial release includes a few core events that cover the key steps of identity and authentication:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authentication Succeeded&lt;/li&gt;
&lt;li&gt;Authentication Failed&lt;/li&gt;
&lt;li&gt;Person Created&lt;/li&gt;
&lt;li&gt;Virtual Page Loaded&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We will be adding more in the near future to further enrich your analytics.&lt;/p&gt;
&lt;h2&gt;The How: Webhooks&lt;/h2&gt;
&lt;p&gt;You can consume SlashID events by registering webhooks for your organization. You can create a webhook with two simple &lt;a href=&quot;https://developer.slashid.dev/docs/category/api/organization-webhooks&quot;&gt;API calls&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a webhook with a target URL&lt;/li&gt;
&lt;li&gt;Specify a trigger for that webhook (for example, an &lt;code&gt;AuthenticationSucceeded&lt;/code&gt; event)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each webhook can have multiple triggers, and multiple webhooks can consume the same trigger, so you’re free to configure your webhooks in whatever way suits your requirements. Once you have registered your webhook, it will be called on each relevant trigger.&lt;/p&gt;
&lt;p&gt;For step-by-step instructions, read our &lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/webhooks&quot;&gt;webhooks guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Security First&lt;/h2&gt;
&lt;p&gt;One of the key aspects of handling webhooks from any provider is security – you must verify that the webhook request originated from a trusted provider, and that the content has not been tampered with. Unfortunately, not all providers make this possible, and when they do, the process is rarely straightforward. At SlashID we take a security-first approach, so we made webhook verification easy and safe.&lt;/p&gt;
&lt;p&gt;The common approach to webhook verification is to send a request with a JSON payload, with a signature embedded somewhere in the request. The signature is based on some concatenation of the JSON body, the URL, and possibly other information, and the process of generating/verifying the signature is often based on a shared secret. This makes your life difficult in myriad ways: you need to manage a secret; you need to either re-implement the verification logic or depend on a library from the provider (assuming they publish one in the language you use); and you’ll probably need to reimplement it anyway for testing. On top of this, there’s no standard for signing HTTP requests, so each provider has their own way of doing it. In one word, it’s a nightmare.&lt;/p&gt;
&lt;p&gt;At SlashID, we use a JSON Web Token (JWT) – just like for our authentication tokens. JWTs are a well-known and widely used standard for securely exchanging information between parties, and pretty much every language has at least one library for handling them. The request body of the webhook call is a signed and encoded JWT. To verify a SlashID webhook call, simply &lt;a href=&quot;https://developer.slashid.dev/docs/api/get-organizations-webhooks-verification-jwks&quot;&gt;retrieve the verification key&lt;/a&gt; and use your library of choice to check the signature and get the JWT payload. Once decoded, you can check the content of the webhook is what you expect. Easy.&lt;/p&gt;
&lt;p&gt;The decoded payload includes some metadata about the call (such as your organization ID and timestamps), and a field called &lt;code&gt;trigger_content&lt;/code&gt;, with information about whatever triggered the webhook. For an event, this would be the contents of the event, as in our event definitions. For more detail, see our &lt;a href=&quot;https://developer.slashid.dev/docs/access/guides/webhooks&quot;&gt;webhooks guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The When: Event Publishing&lt;/h2&gt;
&lt;p&gt;SlashID publishes events in response to user interactions with your frontend using the &lt;a href=&quot;https://developer.slashid.dev/docs/access/sdk&quot;&gt;SlashID SDK&lt;/a&gt;, and from our backend. In most cases events are published automatically and asynchronously behind the scenes, so you don’t need to worry about them. If you want to keep track of page views (for example to monitor the progress of your users through the funnel), the &lt;code&gt;VirtualPageLoaded&lt;/code&gt; event should be published by calling the &lt;a href=&quot;https://developer.slashid.dev/docs/access/sdk/classes/Types.Analytics#trackvirtualpageview&quot;&gt;corresponding method&lt;/a&gt; in the SDK. If you are building a single page application, this method can be called every time client side navigation happens.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;With this release, you can use webhooks to consume SlashID analytics events. We will be adding more events and features in the near future, so stay tuned for release announcements.&lt;/p&gt;
&lt;p&gt;Ready to try SlashID? Sign up &lt;a href=&quot;https://console.slashid.dev/signup?utm_source=webhook-bp&quot;&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 analytics and have some feedback? &lt;a href=&quot;mailto:contact@slashid.dev&quot;&gt;Let us know&lt;/a&gt;!&lt;/p&gt;
</content:encoded><author>Joseph Gardner, Vincenzo Iozzo</author></item></channel></rss>