diff --git a/linkinator.config.json b/linkinator.config.json
index f90fba7..cd4d647 100644
--- a/linkinator.config.json
+++ b/linkinator.config.json
@@ -4,6 +4,7 @@
"www.linkedin.com",
"/js/script.hash.js",
"github.com/hellocoop/hello.dev/*",
- "http://localhost:3000"
+ "http://localhost:3000",
+ "https://blog.hello.dev/*"
]
}
\ No newline at end of file
diff --git a/pages/docs/mockin.mdx b/pages/docs/mockin.mdx
index 47b17e9..70518ed 100644
--- a/pages/docs/mockin.mdx
+++ b/pages/docs/mockin.mdx
@@ -35,7 +35,7 @@ npx @hellocoop/mockin
`docker run -d -p 3333:3333 hellocoop/mockin:latest`
-If you are running Mockin with `docker-compose` or `Kubernetes`, you will need to set the environment variables for the Mockin service to the hostname the service is running at, and similarly will need to configure your app to redirect to that service when the the `[ ō Continue with Hellō ]` button is pressed. See https://github.com/hellocoop/packages/tree/main/tests for a `docker-compose` example using Playwright.
+If you are running Mockin with `docker-compose` or `Kubernetes`, you will need to set the environment variables for the Mockin service to the hostname the service is running at, and similarly will need to configure your app to redirect to that service when the the `[ ō Continue with Hellō ]` button is pressed. See https://github.com/hellocoop/packages-js/tree/main/express/tests for a `docker-compose` example using Playwright.
## Playing with Mockin
You can use Mocking with the [Hellō Playground](https://playground.hello.dev). Run Mockin with either:
diff --git a/pages/docs/oidc/_meta.json b/pages/docs/oidc/_meta.json
index 049d208..2845ed9 100644
--- a/pages/docs/oidc/_meta.json
+++ b/pages/docs/oidc/_meta.json
@@ -1,6 +1,7 @@
{
"request": "Auth Request",
"response": "Auth Response",
+ "device": "Device Code Flow",
"verification": "Verification",
"token": "ID Token",
"config": "Configuration",
diff --git a/pages/docs/oidc/device.mdx b/pages/docs/oidc/device.mdx
new file mode 100644
index 0000000..00b57e6
--- /dev/null
+++ b/pages/docs/oidc/device.mdx
@@ -0,0 +1,58 @@
+import { Callout } from 'nextra/components'
+
+# Device Auth Grant
+
+Hellō supports using the [Device Authorization Grant (RFC8628)](https://datatracker.ietf.org/doc/html/rfc8628) to obtain an ID Token for a user. This enables the user to log into internet connected devices that do not support a browser. In addition to the `client_id` and `scope` parameters, Hellō supports the `nonce`, `prompt`, `login_hint`, `domain_hint`, and `provider_hint`.
+
+## Device Auth Request
+
+To start the process, do an HTTP POST to the Hellō `device_authorization_endpoint`, `https://wallet.hello.coop/oauth/device/code`, with the `Content-Type` of `application/x-www-form-urlencoded` and the following parameters:
+
+|Parameters|Description|
+|---|---|
+|`client_id` *required*|The `client_id` for your app from [console.hello.coop](https://console.hello.coop).|
+|`scope` *required*|The `openid` scope and zero or more space delimited scopes listed at [Hellō Claims](/docs/scopes).|
+|`nonce` *required*|A unique string that will be included in the signed ID Token. This links the ID Token to your request.|
+|`login_hint` *optional*|A hint (`email` or `sub`) for which user account to use. Valid formats: - `login_hint=name@domain.example` - `login_hint=mailto:name@domain.example` - `login_hint=sub_01234567abcdefghABCDEFGH_XXX`|
+|`prompt` *optional*|A space delimited list. Accepted values include:|
+| | - `login` will require the user to re-authenticate at their login provider|
+| | - `consent` will require the user to review, and potentially change, released claims|
+
+|Hellō Parameters|Description|
+|---|---|
+|`provider_hint` *optional*| See [Wallet API \| provider_hint](/docs/apis/wallet/#provider_hint) for details. |
+|`domain_hint` *optional*|A hint for which domain or type of account: |
+| | - `domain.example` to request the user logs in with a specific managed account |
+| | - `managed` to request a managed account |
+| | - `personal` to request a personal (non-managed) account |
+
+
+## Device Authorization Response
+
+If the request is accepted, a JSON response will be provided per [RFC 8628 3.2](https://datatracker.ietf.org/doc/html/rfc8628#section-3.2). Here is a sample response:
+
+```json
+{
+ "device_code": "bde3a264c27b79cfc0e838e098bd193f571cf01de45a871dd228ab889d519878",
+ "user_code": "228-457-485",
+ "verification_uri": "https://wallet.hello.coop/device",
+ "verification_uri_complete": "https://wallet.hello.coop/device?user_code=228-457-485",
+ "expires_in": 300000
+}
+```
+
+## User Interaction
+
+Instruct the user to load the `verification_uri` and enter the `user_code` value, or load the `verification_uri_complete`, which could be presented to the user as a QR code they can scan to start the login.
+
+## Device Access Token Request
+
+Do an HTTP POST with the `Content-Type` of `application/x-www-form-urlencoded` to the Hellō `token_endpoint`, `https://wallet.hello.coop/oauth/token` per [RFC 8628 3.1](https://datatracker.ietf.org/doc/html/rfc8628#section-3.1) passing `grant_type` with a value of `urn:ietf:params:oauth:grant-type:device_code` and the `device_code` and `client_id`. Here is a sample request:
+
+```
+grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&
+device_code=bde3a264c27b79cfc0e838e098bd193f571cf01de45a871dd228ab889d519878&
+client_id=app_exampleapplication_cid
+```
+
+The response will usually be the `token_endpoint` response.
\ No newline at end of file
diff --git a/public/feed.xml b/public/feed.xml
index 5839f00..619019f 100644
--- a/public/feed.xml
+++ b/public/feed.xml
@@ -1,5 +1,5 @@
https://blog.hello.devRSS for NodeTue, 31 Dec 2024 12:31:58 GMT60https://blog.hello.dev/introducing-the-org-claimhttps://blog.hello.dev/introducing-the-org-claimMon, 11 Nov 2024 13:02:36 GMT<![CDATA[<p>With the release of our <a target="_blank" href="https://blog.hello.coop/?p=316">B2B SaaS functionality</a>, we needed a mechanism to communicate which organization a user belongs to. A domain name is a common human memorable identifier for an organization, just like email is for a user. But it has similar <a target="_blank" href="https://learn.microsoft.com/en-us/entra/identity-platform/migrate-off-email-claim-authorization">issues with email</a> being used as the index into a DB, but the domain is similarly useful for linking an existing org record. There are lots of other unique identifiers for organizations including DUNS, LEI, EIN, and VAT. This led us to create an <code>org</code> claim as an object that could hold a Hell <code>org</code> , and a domain name, and other identifiers in the future:</p><pre><code class="lang-json"><span class="hljs-string">"org"</span>: { <span class="hljs-attr">"id"</span>: <span class="hljs-string">"org_9yMcnK3agJyUDxOBp19gpSe8_PU1"</span>, <span class="hljs-attr">"domain"</span>: <span class="hljs-string">"hello.coop"</span>}</code></pre><p>We also wanted to identify which of the claims came from the organization, and created <code>org_claims</code> which lists which claims in the ID Token were issued by the org.</p><p>These new claims ( <code>org</code> and <code>org_claims</code> ) are included in any id_token where the user chose to use a managed account. There is nothing you need to do different as a developer, but you can take advantage of new features when making an authorization request.</p><h2 id="heading-domainhint"><code>domain_hint</code></h2><p>Following the lead of Microsoft and Google, we have added support for <code>domain_hint</code> in the authorization request. This allows an application to indicate which organization they want the user to login with. This is useful for provider initiated login, as including the <code>domain_hint</code> in the URL will signal to Hell which organization to have the user log in with. For example:</p><p><code>https://application.example/api/hellocoop?iss=https%3A%2F%2Fissuer.hello.coop&domain_hint=enterprise.example</code></p><p>Will start a login for a user from <strong>enterprise.example</strong> at <strong>application.example</strong></p><p>If you dont know the organization the user belongs to, and you want to require a managed account, you can include <code>domain_hint=managed</code> in the authorization request. Conversely, if you only want a personal account, you can include <code>domain_hint=personal</code>. See the <a target="_blank" href="https://www.hello.dev/docs/oidc/request/#hell%C5%8D-parameters">Authorization Request</a> documentation for more details, or try it out in the <a target="_blank" href="https://playground.hello.dev/">Hell Playground</a>.</p>]]><![CDATA[<p>With the release of our <a target="_blank" href="https://blog.hello.coop/?p=316">B2B SaaS functionality</a>, we needed a mechanism to communicate which organization a user belongs to. A domain name is a common human memorable identifier for an organization, just like email is for a user. But it has similar <a target="_blank" href="https://learn.microsoft.com/en-us/entra/identity-platform/migrate-off-email-claim-authorization">issues with email</a> being used as the index into a DB, but the domain is similarly useful for linking an existing org record. There are lots of other unique identifiers for organizations including DUNS, LEI, EIN, and VAT. This led us to create an <code>org</code> claim as an object that could hold a Hell <code>org</code> , and a domain name, and other identifiers in the future:</p><pre><code class="lang-json"><span class="hljs-string">"org"</span>: { <span class="hljs-attr">"id"</span>: <span class="hljs-string">"org_9yMcnK3agJyUDxOBp19gpSe8_PU1"</span>, <span class="hljs-attr">"domain"</span>: <span class="hljs-string">"hello.coop"</span>}</code></pre><p>We also wanted to identify which of the claims came from the organization, and created <code>org_claims</code> which lists which claims in the ID Token were issued by the org.</p><p>These new claims ( <code>org</code> and <code>org_claims</code> ) are included in any id_token where the user chose to use a managed account. There is nothing you need to do different as a developer, but you can take advantage of new features when making an authorization request.</p><h2 id="heading-domainhint"><code>domain_hint</code></h2><p>Following the lead of Microsoft and Google, we have added support for <code>domain_hint</code> in the authorization request. This allows an application to indicate which organization they want the user to login with. This is useful for provider initiated login, as including the <code>domain_hint</code> in the URL will signal to Hell which organization to have the user log in with. For example:</p><p><code>https://application.example/api/hellocoop?iss=https%3A%2F%2Fissuer.hello.coop&domain_hint=enterprise.example</code></p><p>Will start a login for a user from <strong>enterprise.example</strong> at <strong>application.example</strong></p><p>If you dont know the organization the user belongs to, and you want to require a managed account, you can include <code>domain_hint=managed</code> in the authorization request. Conversely, if you only want a personal account, you can include <code>domain_hint=personal</code>. See the <a target="_blank" href="https://www.hello.dev/docs/oidc/request/#hell%C5%8D-parameters">Authorization Request</a> documentation for more details, or try it out in the <a target="_blank" href="https://playground.hello.dev/">Hell Playground</a>.</p>]]>https://cdn.hashnode.com/res/hashnode/image/upload/v1731330023653/bf4695a6-fee5-444f-afd2-1e3555b8768a.pnghttps://blog.hello.devRSS for NodeMon, 31 Mar 2025 17:01:19 GMT60https://blog.hello.dev/introducing-the-org-claimhttps://blog.hello.dev/introducing-the-org-claimMon, 11 Nov 2024 13:02:36 GMT<![CDATA[<p>With the release of our <a target="_blank" href="https://blog.hello.coop/?p=316">B2B SaaS functionality</a>, we needed a mechanism to communicate which organization a user belongs to. A domain name is a common human memorable identifier for an organization, just like email is for a user. But it has similar <a target="_blank" href="https://learn.microsoft.com/en-us/entra/identity-platform/migrate-off-email-claim-authorization">issues with email</a> being used as the index into a DB, but the domain is similarly useful for linking an existing org record. There are lots of other unique identifiers for organizations including DUNS, LEI, EIN, and VAT. This led us to create an <code>org</code> claim as an object that could hold a Hell <code>org</code> , and a domain name, and other identifiers in the future:</p><pre><code class="lang-json"><span class="hljs-string">"org"</span>: { <span class="hljs-attr">"id"</span>: <span class="hljs-string">"org_9yMcnK3agJyUDxOBp19gpSe8_PU1"</span>, <span class="hljs-attr">"domain"</span>: <span class="hljs-string">"hello.coop"</span>}</code></pre><p>We also wanted to identify which of the claims came from the organization, and created <code>org_claims</code> which lists which claims in the ID Token were issued by the org.</p><p>These new claims ( <code>org</code> and <code>org_claims</code> ) are included in any id_token where the user chose to use a managed account. There is nothing you need to do different as a developer, but you can take advantage of new features when making an authorization request.</p><h2 id="heading-domainhint"><code>domain_hint</code></h2><p>Following the lead of Microsoft and Google, we have added support for <code>domain_hint</code> in the authorization request. This allows an application to indicate which organization they want the user to login with. This is useful for provider initiated login, as including the <code>domain_hint</code> in the URL will signal to Hell which organization to have the user log in with. For example:</p><p><code>https://application.example/api/hellocoop?iss=https%3A%2F%2Fissuer.hello.coop&domain_hint=enterprise.example</code></p><p>Will start a login for a user from <strong>enterprise.example</strong> at <strong>application.example</strong></p><p>If you dont know the organization the user belongs to, and you want to require a managed account, you can include <code>domain_hint=managed</code> in the authorization request. Conversely, if you only want a personal account, you can include <code>domain_hint=personal</code>. See the <a target="_blank" href="https://www.hello.dev/docs/oidc/request/#hell%C5%8D-parameters">Authorization Request</a> documentation for more details, or try it out in the <a target="_blank" href="https://playground.hello.dev/">Hell Playground</a>.</p>]]><![CDATA[<p>With the release of our <a target="_blank" href="https://blog.hello.coop/?p=316">B2B SaaS functionality</a>, we needed a mechanism to communicate which organization a user belongs to. A domain name is a common human memorable identifier for an organization, just like email is for a user. But it has similar <a target="_blank" href="https://learn.microsoft.com/en-us/entra/identity-platform/migrate-off-email-claim-authorization">issues with email</a> being used as the index into a DB, but the domain is similarly useful for linking an existing org record. There are lots of other unique identifiers for organizations including DUNS, LEI, EIN, and VAT. This led us to create an <code>org</code> claim as an object that could hold a Hell <code>org</code> , and a domain name, and other identifiers in the future:</p><pre><code class="lang-json"><span class="hljs-string">"org"</span>: { <span class="hljs-attr">"id"</span>: <span class="hljs-string">"org_9yMcnK3agJyUDxOBp19gpSe8_PU1"</span>, <span class="hljs-attr">"domain"</span>: <span class="hljs-string">"hello.coop"</span>}</code></pre><p>We also wanted to identify which of the claims came from the organization, and created <code>org_claims</code> which lists which claims in the ID Token were issued by the org.</p><p>These new claims ( <code>org</code> and <code>org_claims</code> ) are included in any id_token where the user chose to use a managed account. There is nothing you need to do different as a developer, but you can take advantage of new features when making an authorization request.</p><h2 id="heading-domainhint"><code>domain_hint</code></h2><p>Following the lead of Microsoft and Google, we have added support for <code>domain_hint</code> in the authorization request. This allows an application to indicate which organization they want the user to login with. This is useful for provider initiated login, as including the <code>domain_hint</code> in the URL will signal to Hell which organization to have the user log in with. For example:</p><p><code>https://application.example/api/hellocoop?iss=https%3A%2F%2Fissuer.hello.coop&domain_hint=enterprise.example</code></p><p>Will start a login for a user from <strong>enterprise.example</strong> at <strong>application.example</strong></p><p>If you dont know the organization the user belongs to, and you want to require a managed account, you can include <code>domain_hint=managed</code> in the authorization request. Conversely, if you only want a personal account, you can include <code>domain_hint=personal</code>. See the <a target="_blank" href="https://www.hello.dev/docs/oidc/request/#hell%C5%8D-parameters">Authorization Request</a> documentation for more details, or try it out in the <a target="_blank" href="https://playground.hello.dev/">Hell Playground</a>.</p>]]>https://cdn.hashnode.com/res/hashnode/image/upload/v1731330023653/bf4695a6-fee5-444f-afd2-1e3555b8768a.pnghttps://blog.hello.dev/new-hello-identifiershttps://blog.hello.dev/new-hello-identifiersSun, 30 Jun 2024 20:57:15 GMT<![CDATA[<p><strong><em>Updated July 1, 2024 - we will continue to provide UUIDv4 identifiers to our existing customers.</em></strong></p><p>Identifiers are a foundational component of any computing system, and key to an identity service. At Hell, we have:</p><ol><li><p>external identifiers created by other systems (email address, OpenID Connect <code>sub</code> );</p></li><li><p>internal identifiers used to manage relationships between objects (internal user identifier, publisher identifier); and</p></li><li><p>identifiers exposed to other systems (<code>client_id</code>, <code>sub</code>, <code>jti</code>, authorization code).</p></li></ol><p>We of course don't have control over external identifiers. Our requirements for our identifiers are:</p><ul><li><p><strong>Not Leaky</strong> - we don't want the identifiers to leak information about our internal systems, or about our users. This eliminates identifiers tied to machine identity, and identifiers tied to time or being sequential in nature.</p></li><li><p><strong>Distributed Generation</strong> - we want each instance to be able to generate its own identifiers and not have a central service bottleneck. This leads to a random identifier that has a large enough entropy that duplicates are extremely improbable.</p></li><li><p><strong>URL Safe</strong> - we want to be able to pass the identifiers around in URLs and HTTP headers without concern of mismatched encoding, and making them easy to identify when part of a URL or HTTP header.</p></li><li><p><strong>Widely Used</strong> - we don't want to invent our own random numbers. We want a proven implementation.</p></li></ul><p>The popular choice today, which is what we chose, is <a target="_blank" href="https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)">UUID v4</a>. This is what one looks like in string format:</p><p><code>a9ab46e7-a526-43e7-9e18-458c76c2f5f4</code></p><p>UUIDs use a base 16 (hex) alphabet, have 32 characters, and 4 dashes for readability. The version is indicated by the digit <code>4</code> at the start of the 3rd set. Earlier versions generated values using a combination of machine identity and time. Version 4 is random, which is much simpler to do now with modern processors. With 122 bits of entropy (6 bits are reserved for version), the odds of generating a duplicate identifier for each person on the planet is 6.04 x 10-19 (<a target="_blank" href="https://chatgpt.com/share/fde9a089-3bfe-41a9-8150-cbbe841cae37">thanks ChatGPT</a> -- note the link uses a UUID v4 identifier!).</p><p>We chose to use the standard string formatting for UUIDs, which are 36 characters long. While a binary format would save storage and bandwidth, simpler code wins in the era of cloud computing where Hell is deployed.</p><p>While UUID v4 identifiers met our security, privacy, and entropy requirements, we encountered friction working with them.</p><h3 id="heading-uuids-all-look-the-same">UUIDs All Look the Same</h3><p>While it is a feature that they are indifferentiable from each other, when writing and testing code with a bunch of different identifiers in the same system, we don't know what the identifier represents. We use DynamoDB and the recommended single table architecture, so we have a mix of record types all in the same index, and we added a <code>type</code> property to help, but we are then looking in multiple locations. I had seen some implementations prefixing UUIDs with the identifier type, and appreciated how it simplified development.</p><p>We chose a set of 3 character prefixes that indicate at a glance the identifier type. Our initial set:</p><pre><code class="lang-javascript"> <span class="hljs-comment">// Wallet created</span>{ <span class="hljs-attr">usr</span>: <span class="hljs-string">'Hell internal user identifier'</span>, <span class="hljs-attr">hdi</span>: <span class="hljs-string">'Hell directed identifier - sub value in ID token'</span>, <span class="hljs-attr">jti</span>: <span class="hljs-string">'ID Token jti'</span>, <span class="hljs-attr">kid</span>: <span class="hljs-string">'Hell key identifier in ID Token header'</span>, <span class="hljs-attr">ses</span>: <span class="hljs-string">'Hell session identifier'</span>, <span class="hljs-attr">dvc</span>: <span class="hljs-string">'Hell device cookie identifier'</span>, <span class="hljs-attr">inv</span>: <span class="hljs-string">'Hell invitation identifier'</span>, <span class="hljs-attr">pky</span>: <span class="hljs-string">'Hell passkey identifier'</span>, <span class="hljs-attr">pic</span>: <span class="hljs-string">'Hell picture identifier'</span>, <span class="hljs-attr">non</span>: <span class="hljs-string">'Hell nonce identifier'</span>, <span class="hljs-attr">cod</span>: <span class="hljs-string">"Hell authorization code"</span>, <span class="hljs-comment">// Admin created</span> <span class="hljs-attr">pub</span>: <span class="hljs-string">'Hell publisher identifier'</span>, <span class="hljs-attr">app</span>: <span class="hljs-string">'Hell application identifier (client_id)'</span>,}</code></pre><h3 id="heading-dashes-suck-when-selecting-text">Dashes Suck When Selecting Text</h3><p>A common task is to select an identifier to be copied and then pasted somewhere else. When double clicking on a string, editors, browsers, and terminals will select the word being clicked, and most editors consider a dash to be a word separator, so only part of the identifier is selected (try it out):</p><p>'a9ab46e7-a526-<mark>43e7</mark>-9e18-458c76c2f5f4'</p><p>Triple clicking will often increase the selection, but then the quotes are included in the selection, which is often not what is desired:</p><p><mark>'a9ab46e7-a526-43e7-9e18-458c76c2f5f4'</mark></p><p>This led us on a search for an alternative random number generator that met our requirements and we came across <a target="_blank" href="https://github.com/ai/nanoid">nanoids</a>, which are shorter, and allow a custom alphabet. We chose 0-9a-zA-Z as the alphabet, the underscore as a separator. While base62 is not a nice neat power of 2, using only alphanumeric characters as the custom alphabet reduces the cognitive dissonance glancing at them.</p><h3 id="heading-uuids-are-hard-to-visually-correlate">UUIDs are Hard to Visually Correlate</h3><p>UUIDs start with an 8 char string, and end with a 12 char string. When looking at a set of identifiers, either in a column or across variables while debugging, it is difficult to identify if an identifier is the same at a glance. Using just the first digit has a 1 in 16 chance of collision. Using the first two digits has a 1 in 256 chance of collision - low enough to cause confusion. Visually selecting more than two digits without a separator was challenging.</p><p>As we were now prefixing our identifiers, a short suffix with enough entropy for quick visual correlation appeared a good solution. Two digits of base 62 is 1 in 3,844. Three digits is 1 in 238,328. This leads us to an identifier that looks something like:</p><p><code>typ_0123456789abcdefABCDEF_xyz</code></p><p>This jumps out as one of our identifiers, we know at a glance the type, and the suffix is easy to use to match another variable value across contexts.</p><h3 id="heading-other-design-factors">Other Design Factors</h3><p><strong>Identifier Validation:</strong> Being able to programmatically test if an identifier is valid can be useful in detect errors in a system. Having a consistent length, format, and character set can detect if something is not an identifier. The type prefix helps detect if identifiers got mixed up. Why not add in a checksum as well?</p><p><strong>Identifier Length:</strong> A nanoid using a 62 character alphabet only needs to be 22 characters long to exceed the entropy of a UUID. While we were ok with making the computer work harder with a 62 alphabet instead of 64, we wanted an easy to remember length. With a 4 character prefix, and an underscore separating the suffix, that gave us 27 characters. 32 is easier for programmers to remember, and a 24 character random string is also easy to remember, allowing us to use the 3 character suffix as a checksum.</p><h2 id="heading-new-hello-identifiers">New Hell Identifiers</h2><p>Here is an example of a column of our new identifiers in a:</p><pre><code class="lang-plaintext">app_JbkuwjnRPIxuerq765q4IOXO_rc2sub_To8aelKK5rOpeLesEJA0VawX_TW7app_Cd5iWmdENXTYqJw6o07FuRKn_pUMpub_PDOzPRqBuZjBcrfG9oh4M0oN_3qFapp_Zpa1TgesIRna5nDKtWMp11cV_jlHsub_76t2ITgp6wRMBcyHhgUOM2pQ_v7Aapp_wcmPSIaiPuLtCa8Yp0Iwhwfm_IACpub_PDOzPRqBuZjBcrfG9oh4M0oN_3qFapp_FAZ9eZ8NgtauhQp5bnXXE1W1_oi3</code></pre><p>We know these are our identifiers at a glance. We can see they are the <code>app</code>, <code>sub</code> and <code>pub</code> types. We can quickly find the two <code>pub</code> identifiers, and see they (likely) have the same value with the <code>3qF</code> suffix. We are finding it useful, and hope that they also help our customers when working with our identifiers. And a double click on the value selects the whole value!</p><h3 id="heading-deployment-considerations">Deployment Considerations</h3><p>All of our identifiers are opaque strings as far as the logic is concerned. The only logic requirement is that they are a string and are unique in the system. This allows us to change the identifier format without any impact on the logic, or our customers.</p><p>While we already have UUID identifiers being used in production by our customers, our objective is to help us when doing development and testing.</p><p>How about the change in performance? I did a little test of 1M generations:</p><ul><li><p>45 ms for UUIDv4</p></li><li><p>160 ms for a standard 21 char nanoid</p></li><li><p>360 ms for a 27 char nanoid with custom alphabet</p></li><li><p>470 ms for a 24 char nanoid and 3 digit checksum</p></li></ul><p>I was surprised how much slower nanoid was compared to UUID, but then again, the UUID is now native code in node. The custom alphabet and checksum tripled the time over the default nanoid, and a 10X change over UUID. Worse than I had hoped, but the impact on user experience is irrelevant as each one takes half a nanosecond.</p><p>Even though we don't expect there to be any customer impact, we will hold off deployment until we have informed our customers and received any feedback.</p><p>We have made our library open source and available at:</p><p><a target="_blank" href="https://github.com/hellocoop/packages/tree/main/identifier">https://github.com/hellocoop/packages</a></p><p>and available as an npm package as <code>@hellocoop/identifiers</code>.</p><p>The build process is somewhat of a hack, but that is another story about wanting to support both CommonJS and ECMA Script modules, and have a single source of truth for our list of identifier types.</p><p><strong><em>Updated July 1, 2024 - we will continue to provide UUIDv4 identifiers to our existing customers.</em></strong></p><p>A few of our customers implementations take advantage of the <code>sub</code> in the ID Token being a UUID and have configured their DB schema accordingly. Our data model has directed users (the <code>sub</code> in the ID Token) belonging to publishers, and applications (<code>client_id</code> for developer, and <code>aud</code> in the ID Token) belonging to publishers.</p><p>Any existing publishers (that have a UUID identifier), or any new ones created that check a box to use UUIDs, will continue to use UUIDs and won't receive the new identifiers. Any publisher that has an the new format, will have all their user and application identifiers in the new format, providing consistency in the identifier format.</p>]]><![CDATA[<p><strong><em>Updated July 1, 2024 - we will continue to provide UUIDv4 identifiers to our existing customers.</em></strong></p><p>Identifiers are a foundational component of any computing system, and key to an identity service. At Hell, we have:</p><ol><li><p>external identifiers created by other systems (email address, OpenID Connect <code>sub</code> );</p></li><li><p>internal identifiers used to manage relationships between objects (internal user identifier, publisher identifier); and</p></li><li><p>identifiers exposed to other systems (<code>client_id</code>, <code>sub</code>, <code>jti</code>, authorization code).</p></li></ol><p>We of course don't have control over external identifiers. Our requirements for our identifiers are:</p><ul><li><p><strong>Not Leaky</strong> - we don't want the identifiers to leak information about our internal systems, or about our users. This eliminates identifiers tied to machine identity, and identifiers tied to time or being sequential in nature.</p></li><li><p><strong>Distributed Generation</strong> - we want each instance to be able to generate its own identifiers and not have a central service bottleneck. This leads to a random identifier that has a large enough entropy that duplicates are extremely improbable.</p></li><li><p><strong>URL Safe</strong> - we want to be able to pass the identifiers around in URLs and HTTP headers without concern of mismatched encoding, and making them easy to identify when part of a URL or HTTP header.</p></li><li><p><strong>Widely Used</strong> - we don't want to invent our own random numbers. We want a proven implementation.</p></li></ul><p>The popular choice today, which is what we chose, is <a target="_blank" href="https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)">UUID v4</a>. This is what one looks like in string format:</p><p><code>a9ab46e7-a526-43e7-9e18-458c76c2f5f4</code></p><p>UUIDs use a base 16 (hex) alphabet, have 32 characters, and 4 dashes for readability. The version is indicated by the digit <code>4</code> at the start of the 3rd set. Earlier versions generated values using a combination of machine identity and time. Version 4 is random, which is much simpler to do now with modern processors. With 122 bits of entropy (6 bits are reserved for version), the odds of generating a duplicate identifier for each person on the planet is 6.04 x 10-19 (<a target="_blank" href="https://chatgpt.com/share/fde9a089-3bfe-41a9-8150-cbbe841cae37">thanks ChatGPT</a> -- note the link uses a UUID v4 identifier!).</p><p>We chose to use the standard string formatting for UUIDs, which are 36 characters long. While a binary format would save storage and bandwidth, simpler code wins in the era of cloud computing where Hell is deployed.</p><p>While UUID v4 identifiers met our security, privacy, and entropy requirements, we encountered friction working with them.</p><h3 id="heading-uuids-all-look-the-same">UUIDs All Look the Same</h3><p>While it is a feature that they are indifferentiable from each other, when writing and testing code with a bunch of different identifiers in the same system, we don't know what the identifier represents. We use DynamoDB and the recommended single table architecture, so we have a mix of record types all in the same index, and we added a <code>type</code> property to help, but we are then looking in multiple locations. I had seen some implementations prefixing UUIDs with the identifier type, and appreciated how it simplified development.</p><p>We chose a set of 3 character prefixes that indicate at a glance the identifier type. Our initial set:</p><pre><code class="lang-javascript"> <span class="hljs-comment">// Wallet created</span>{ <span class="hljs-attr">usr</span>: <span class="hljs-string">'Hell internal user identifier'</span>, <span class="hljs-attr">hdi</span>: <span class="hljs-string">'Hell directed identifier - sub value in ID token'</span>, <span class="hljs-attr">jti</span>: <span class="hljs-string">'ID Token jti'</span>, <span class="hljs-attr">kid</span>: <span class="hljs-string">'Hell key identifier in ID Token header'</span>, <span class="hljs-attr">ses</span>: <span class="hljs-string">'Hell session identifier'</span>, <span class="hljs-attr">dvc</span>: <span class="hljs-string">'Hell device cookie identifier'</span>, <span class="hljs-attr">inv</span>: <span class="hljs-string">'Hell invitation identifier'</span>, <span class="hljs-attr">pky</span>: <span class="hljs-string">'Hell passkey identifier'</span>, <span class="hljs-attr">pic</span>: <span class="hljs-string">'Hell picture identifier'</span>, <span class="hljs-attr">non</span>: <span class="hljs-string">'Hell nonce identifier'</span>, <span class="hljs-attr">cod</span>: <span class="hljs-string">"Hell authorization code"</span>, <span class="hljs-comment">// Admin created</span> <span class="hljs-attr">pub</span>: <span class="hljs-string">'Hell publisher identifier'</span>, <span class="hljs-attr">app</span>: <span class="hljs-string">'Hell application identifier (client_id)'</span>,}</code></pre><h3 id="heading-dashes-suck-when-selecting-text">Dashes Suck When Selecting Text</h3><p>A common task is to select an identifier to be copied and then pasted somewhere else. When double clicking on a string, editors, browsers, and terminals will select the word being clicked, and most editors consider a dash to be a word separator, so only part of the identifier is selected (try it out):</p><p>'a9ab46e7-a526-<mark>43e7</mark>-9e18-458c76c2f5f4'</p><p>Triple clicking will often increase the selection, but then the quotes are included in the selection, which is often not what is desired:</p><p><mark>'a9ab46e7-a526-43e7-9e18-458c76c2f5f4'</mark></p><p>This led us on a search for an alternative random number generator that met our requirements and we came across <a target="_blank" href="https://github.com/ai/nanoid">nanoids</a>, which are shorter, and allow a custom alphabet. We chose 0-9a-zA-Z as the alphabet, the underscore as a separator. While base62 is not a nice neat power of 2, using only alphanumeric characters as the custom alphabet reduces the cognitive dissonance glancing at them.</p><h3 id="heading-uuids-are-hard-to-visually-correlate">UUIDs are Hard to Visually Correlate</h3><p>UUIDs start with an 8 char string, and end with a 12 char string. When looking at a set of identifiers, either in a column or across variables while debugging, it is difficult to identify if an identifier is the same at a glance. Using just the first digit has a 1 in 16 chance of collision. Using the first two digits has a 1 in 256 chance of collision - low enough to cause confusion. Visually selecting more than two digits without a separator was challenging.</p><p>As we were now prefixing our identifiers, a short suffix with enough entropy for quick visual correlation appeared a good solution. Two digits of base 62 is 1 in 3,844. Three digits is 1 in 238,328. This leads us to an identifier that looks something like:</p><p><code>typ_0123456789abcdefABCDEF_xyz</code></p><p>This jumps out as one of our identifiers, we know at a glance the type, and the suffix is easy to use to match another variable value across contexts.</p><h3 id="heading-other-design-factors">Other Design Factors</h3><p><strong>Identifier Validation:</strong> Being able to programmatically test if an identifier is valid can be useful in detect errors in a system. Having a consistent length, format, and character set can detect if something is not an identifier. The type prefix helps detect if identifiers got mixed up. Why not add in a checksum as well?</p><p><strong>Identifier Length:</strong> A nanoid using a 62 character alphabet only needs to be 22 characters long to exceed the entropy of a UUID. While we were ok with making the computer work harder with a 62 alphabet instead of 64, we wanted an easy to remember length. With a 4 character prefix, and an underscore separating the suffix, that gave us 27 characters. 32 is easier for programmers to remember, and a 24 character random string is also easy to remember, allowing us to use the 3 character suffix as a checksum.</p><h2 id="heading-new-hello-identifiers">New Hell Identifiers</h2><p>Here is an example of a column of our new identifiers in a:</p><pre><code class="lang-plaintext">app_JbkuwjnRPIxuerq765q4IOXO_rc2sub_To8aelKK5rOpeLesEJA0VawX_TW7app_Cd5iWmdENXTYqJw6o07FuRKn_pUMpub_PDOzPRqBuZjBcrfG9oh4M0oN_3qFapp_Zpa1TgesIRna5nDKtWMp11cV_jlHsub_76t2ITgp6wRMBcyHhgUOM2pQ_v7Aapp_wcmPSIaiPuLtCa8Yp0Iwhwfm_IACpub_PDOzPRqBuZjBcrfG9oh4M0oN_3qFapp_FAZ9eZ8NgtauhQp5bnXXE1W1_oi3</code></pre><p>We know these are our identifiers at a glance. We can see they are the <code>app</code>, <code>sub</code> and <code>pub</code> types. We can quickly find the two <code>pub</code> identifiers, and see they (likely) have the same value with the <code>3qF</code> suffix. We are finding it useful, and hope that they also help our customers when working with our identifiers. And a double click on the value selects the whole value!</p><h3 id="heading-deployment-considerations">Deployment Considerations</h3><p>All of our identifiers are opaque strings as far as the logic is concerned. The only logic requirement is that they are a string and are unique in the system. This allows us to change the identifier format without any impact on the logic, or our customers.</p><p>While we already have UUID identifiers being used in production by our customers, our objective is to help us when doing development and testing.</p><p>How about the change in performance? I did a little test of 1M generations:</p><ul><li><p>45 ms for UUIDv4</p></li><li><p>160 ms for a standard 21 char nanoid</p></li><li><p>360 ms for a 27 char nanoid with custom alphabet</p></li><li><p>470 ms for a 24 char nanoid and 3 digit checksum</p></li></ul><p>I was surprised how much slower nanoid was compared to UUID, but then again, the UUID is now native code in node. The custom alphabet and checksum tripled the time over the default nanoid, and a 10X change over UUID. Worse than I had hoped, but the impact on user experience is irrelevant as each one takes half a nanosecond.</p><p>Even though we don't expect there to be any customer impact, we will hold off deployment until we have informed our customers and received any feedback.</p><p>We have made our library open source and available at:</p><p><a target="_blank" href="https://github.com/hellocoop/packages/tree/main/identifier">https://github.com/hellocoop/packages</a></p><p>and available as an npm package as <code>@hellocoop/identifiers</code>.</p><p>The build process is somewhat of a hack, but that is another story about wanting to support both CommonJS and ECMA Script modules, and have a single source of truth for our list of identifier types.</p><p><strong><em>Updated July 1, 2024 - we will continue to provide UUIDv4 identifiers to our existing customers.</em></strong></p><p>A few of our customers implementations take advantage of the <code>sub</code> in the ID Token being a UUID and have configured their DB schema accordingly. Our data model has directed users (the <code>sub</code> in the ID Token) belonging to publishers, and applications (<code>client_id</code> for developer, and <code>aud</code> in the ID Token) belonging to publishers.</p><p>Any existing publishers (that have a UUID identifier), or any new ones created that check a box to use UUIDs, will continue to use UUIDs and won't receive the new identifiers. Any publisher that has an the new format, will have all their user and application identifiers in the new format, providing consistency in the identifier format.</p>]]>https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/VMKBFR6r_jg/upload/581fc88ffadf3f535c8db2dfbd284868.jpeg
-https://www.hello.dev/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/apis/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/apis/admin/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/apis/invite/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/apis/quickstart/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/apis/wallet/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/apis/web-client/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/buttons/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/comparison/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/getting-started/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/mockin/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/oidc/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/oidc/config/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/oidc/errors/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/oidc/request/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/oidc/response/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/oidc/token/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/oidc/unsupported/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/oidc/verification/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/quickstarts/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/quickstarts/express/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/quickstarts/fastify/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/quickstarts/nextjs/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/quickstarts/wordpress/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/roadmap/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/scopes/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/config/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/environment/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/express/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/faqs/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/fastify/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/helper/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/nextjs/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/quickstart/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/react/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/svelte/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/docs/sdks/vue/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/faqs/2024-12-31T12:36:40.009Zdaily0.7
-https://www.hello.dev/pricing/2024-12-31T12:36:40.009Zdaily0.7
+https://www.hello.dev/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/apis/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/apis/admin/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/apis/invite/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/apis/quickstart/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/apis/wallet/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/apis/web-client/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/buttons/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/comparison/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/getting-started/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/mockin/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/oidc/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/oidc/config/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/oidc/device/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/oidc/errors/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/oidc/request/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/oidc/response/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/oidc/token/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/oidc/unsupported/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/oidc/verification/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/quickstarts/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/quickstarts/express/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/quickstarts/fastify/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/quickstarts/nextjs/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/quickstarts/wordpress/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/roadmap/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/scopes/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/config/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/environment/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/express/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/faqs/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/fastify/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/helper/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/nextjs/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/quickstart/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/react/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/svelte/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/docs/sdks/vue/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/faqs/2025-03-31T17:08:21.163Zdaily0.7
+https://www.hello.dev/pricing/2025-03-31T17:08:21.163Zdaily0.7
\ No newline at end of file