<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Takahiko Kawasaki on Medium]]></title>
        <description><![CDATA[Stories by Takahiko Kawasaki on Medium]]></description>
        <link>https://medium.com/@darutk?source=rss-f485feca545e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*NGi0H5aFfKuKv80gEubrog.jpeg</url>
            <title>Stories by Takahiko Kawasaki on Medium</title>
            <link>https://medium.com/@darutk?source=rss-f485feca545e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 22:56:51 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@darutk/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[How a Lazy Programmer Implements the Shared Signals Framework]]></title>
            <link>https://darutk.medium.com/ssf-846d98076a20?source=rss-f485feca545e------2</link>
            <guid isPermaLink="false">https://medium.com/p/846d98076a20</guid>
            <category><![CDATA[shared-signals]]></category>
            <category><![CDATA[authlete-arena]]></category>
            <category><![CDATA[oauth]]></category>
            <dc:creator><![CDATA[Takahiko Kawasaki]]></dc:creator>
            <pubDate>Fri, 05 Jun 2026 23:56:56 GMT</pubDate>
            <atom:updated>2026-06-05T23:56:56.813Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>I presented a sponsor session on the <a href="https://openid.net/specs/openid-sharedsignals-framework-1_0.html"><strong>Shared Signals Framework</strong></a> (<strong>SSF</strong>) at the <a href="https://oauth.secworkshop.events/osw2026"><strong>OAuth Security Workshop 2026</strong></a> (<strong>OSW2026</strong>), held in Leipzig, Germany, from May 27 through May 29, 2026. This article summarizes the key points of that presentation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fBfefArbMvjCBRP_MtcPUA.jpeg" /><figcaption>Group photo of the Authlete team at OSW2026</figcaption></figure><h3>Three Shared Signals Final Specifications</h3><p>In September 2025, the <a href="https://openid.net/"><strong>OpenID Foundation</strong></a> (<strong>OIDF</strong>) <a href="https://openid.net/three-shared-signals-final-specifications-approved">announced</a> that the final versions of three specifications related to Shared Signals had been approved.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mgjddresZ7R3MFTR-Md9eg.png" /></figure><p>The three specifications are listed below:</p><ul><li><strong>SSF</strong> — <a href="https://openid.net/specs/openid-sharedsignals-framework-1_0.html">OpenID Shared Signals Framework Specification 1.0</a></li><li><strong>CAEP</strong> — <a href="https://openid.net/specs/openid-caep-1_0.html">OpenID Continuous Access Evaluation Profile 1.0</a></li><li><strong>RISC</strong> — <a href="https://openid.net/specs/openid-risc-1_0.html">OpenID RISC Profile Specification 1.0</a></li></ul><p>In addition to these, there are several related specifications under the stewardship of the <a href="https://www.ietf.org/"><strong>Internet Engineering Task Force</strong></a> (<strong>IETF</strong>), including the following:</p><ul><li><strong>RFC 8417</strong> — <a href="https://www.rfc-editor.org/rfc/rfc8417.html">Security Event Token (SET)</a></li><li><strong>RFC 8935</strong> — <a href="https://www.rfc-editor.org/rfc/rfc8935.html">Push-Based Security Event Token (SET) Delivery Using HTTP</a></li><li><strong>RFC 8936</strong> — <a href="https://www.rfc-editor.org/rfc/rfc8936.html">Poll-Based Security Event Token (SET) Delivery Using HTTP</a></li><li><strong>RFC 9493</strong> — <a href="https://www.rfc-editor.org/rfc/rfc9493.html">Subject Identifiers for Security Event Tokens</a></li><li><strong>RFC 9967</strong> — <a href="https://www.rfc-editor.org/rfc/rfc9967.html">System for Cross-Domain Identity Management (SCIM) Profile for Security Event Tokens (SETs)</a></li></ul><h3>Transmitter’s Behavior</h3><p>The <strong>Shared Signals Framework</strong> (<strong>SSF</strong>) specification defines the behavior of a <strong>Transmitter</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GqeyUYqfS5U8fHBXXhyKAA.png" /></figure><p>Broadly speaking, a transmitter has two responsibilities:</p><ol><li>Generate a <strong>Security Event Token</strong> (<strong>SET</strong>).</li><li>Deliver the Security Event Token to a <strong>Receiver</strong>.</li></ol><p>A Security Event Token (SET) is a token that represents a security-related event. Its format is based on JWT. The detailed definition can be found in <a href="https://www.rfc-editor.org/rfc/rfc8417.html">RFC 8417</a>, and the SSF specification introduces additional requirements on top of RFC 8417, including some that conflict with it (for example, SSF prohibits the inclusion of the sub claim).</p><p>Two notable claims in a Security Event Token payload are sub_id and events.</p><h4>sub_id</h4><p>The sub_id claim identifies the subject of the event. Unlike the sub claim in an ID Token, which is a single string value, the value of the sub_id claim is a JSON object whose structure is more complex than one might expect.</p><p><a href="https://www.rfc-editor.org/rfc/rfc9493.html">RFC 9493</a> defines the possible JSON object formats that a sub_id claim may take, and the SSF specification introduces additional definitions and requirements of its own.</p><p><em>From RFC 9493, </em><a href="https://www.rfc-editor.org/rfc/rfc9493.html#section-3.2.3"><em>3.2.3. Issuer and Subject Identifier Format</em></a>:</p><pre>{<br>  &quot;format&quot;: &quot;iss_sub&quot;,<br>  &quot;iss&quot;: &quot;https://issuer.example.com/&quot;,<br>  &quot;sub&quot;: &quot;145234573&quot;<br>}</pre><p><em>From SSF, </em><a href="https://openid.net/specs/openid-sharedsignals-framework-1_0.html#section-3.5.3"><em>3.5.3. IP Addresses Subject Identifier Format</em></a>:</p><pre>{<br>  &quot;format&quot;: &quot;ip-addresses&quot;,<br>  &quot;ip-addresses&quot;: [<br>    &quot;10.29.37.75&quot;,<br>    &quot;2001:0db8:0000:0000:0000:8a2e:0370:7334&quot;<br>  ]<br>}</pre><h4>events</h4><p>The events claim describes the event type and any associated event data. Its value is a JSON object containing exactly one key-value pair. The key is a URI that identifies the event type. The value is another JSON object that may contain event-specific information. If no additional information is associated with the event, the value is simply an empty JSON object.</p><p>The <a href="https://openid.net/specs/openid-caep-1_0.html">CAEP profile</a>, the <a href="https://openid.net/specs/openid-risc-1_0.html">RISC profile</a>, and <a href="https://www.rfc-editor.org/rfc/rfc9967.html">RFC 9967</a> define a number of event type identifiers and the formats of the associated event data that they may carry. The SSF specification itself also defines several event type identifiers.</p><p><em>From CAEP, </em><a href="https://openid.net/specs/openid-caep-1_0.html#section-3.1"><em>3.1. Session Revoked</em></a>:</p><pre>&quot;events&quot;: {<br>  &quot;https://schemas.openid.net/secevent/caep/event-type/session-revoked&quot;: {<br>    &quot;event_timestamp&quot;: 1615304991<br>  }<br>}</pre><p><em>From RISC, </em><a href="https://openid.net/specs/openid-risc-1_0.html#section-2.3"><em>2.3. Account Disabled</em></a>:</p><pre>&quot;events&quot;: {<br>  &quot;https://schemas.openid.net/secevent/risc/event-type/account-disabled&quot;: {<br>    &quot;reason&quot;: &quot;hijacking&quot;<br>  }<br>}</pre><p><em>From RFC 9967, </em><a href="https://www.rfc-editor.org/rfc/rfc9967.html#section-2.4.5"><em>2.4.5. urn:ietf:params:scim:event:prov:activate</em></a>:</p><pre>&quot;events&quot;:{<br>  &quot;urn:ietf:params:scim:event:prov:activate&quot;: {}<br>}</pre><p><em>From SSF, </em><a href="https://openid.net/specs/openid-sharedsignals-framework-1_0.html#section-8.1.4.2"><em>8.1.4.2. Triggering a Verification Event</em></a>:</p><pre>&quot;events&quot;: {<br>  &quot;https://schemas.openid.net/secevent/ssf/event-type/verification&quot;: {<br>    &quot;state&quot;: &quot;VGhpcyBpcyBhbiBleGFtcGxlIHN0YXRlIHZhbHVlLgo=&quot;<br>  }<br>}</pre><h3>Transmitter Breakdown</h3><p>The SSF specification defines a set of endpoints for transmitters.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fGQkLE042SZqCxXxBq6OLQ.png" /></figure><ul><li><strong>Metadata Endpoint</strong> — Publishes the transmitter’s metadata (configuration information). The endpoint path is /.well-known/ssf-configuration (see the specification for the exact definition).</li><li><strong>Configuration Endpoint</strong> — Manages streams, including stream creation, deletion, configuration retrieval, and configuration updates. (Events are delivered through streams.)</li><li><strong>Status Endpoint </strong>— Queries and Updates the status of a stream.</li><li><strong>Add Subject Endpoint</strong> — Addes subjects to a stream’s set of monitored subjects.</li><li><strong>Remove Subject Endpoint</strong> — Removes subjects from a stream’s set of monitored subjects.</li><li><strong>Verification Endpoint</strong> — Tests stream connectivity.</li><li><strong>Polling Endpoint</strong> — Delivers events via polling. (Details are defined in <a href="https://www.rfc-editor.org/rfc/rfc8936.html">RFC 8936</a>)</li></ul><p>In addition, a transmitter needs to publish the public keys required to verify the signatures of Security Event Tokens. As a result, it typically provides an endpoint that exposes a JWK Set document (<a href="https://www.rfc-editor.org/rfc/rfc7517.html">RFC 7517</a>). In fact, transmitter metadata includes the jwks_uri parameter, which indicates the location of the JWK Set document.</p><p>The SSF specification expects these endpoints to be protected by some form of access control. OAuth access tokens, for example, are one possible protection mechanism.</p><p>Endpoints may also be protected by mechanisms other than access tokens. In fact, one open-source implementation protects its endpoints using OAuth client authentication.</p><p>In any case, to satisfy the requirements of the specification, the endpoints must be able to identify the OAuth client making the request. Although this requirement is not stated explicitly, it is an inevitable consequence of the specification’s design.</p><p>Furthermore, although the SSF specification does not define such functionality, a generic transmitter implementation would typically provide a mechanism for enqueueing arbitrary events into its event queue.</p><h3>SSF is Not Trivial to Implement</h3><p>Although the motivation behind the SSF family of specifications is the notification of security-related events, a closer look at the specifications reveals that SSF is actually a highly generic framework. It can be used not only for security events, but for arbitrary events as well.</p><p>As a result, not only the identity providers envisioned by the specifications, but virtually any source of events can act as a transmitter.</p><p>This naturally leads to the idea of adding transmitter functionality to various event sources. Unfortunately, implementing an SSF transmitter is not a particularly simple task. At a minimum, it requires implementing the following:</p><ul><li>Event queue management</li><li>Event delivery via polling and push mechanisms</li><li>Endpoint implementation</li><li>Endpoint protection (OAuth, <a href="https://www.rfc-editor.org/rfc/rfc8705.html">MTLS</a>, <a href="https://www.rfc-editor.org/rfc/rfc9449.html">DPoP</a>, <a href="https://www.rfc-editor.org/rfc/rfc9421.html">HTTP Message Signatures</a>, etc.)</li><li>Key management (for Security Event Token signing, HTTP Message Signatures, and more)</li></ul><p>Reimplementing all of this functionality for every transmitter is a tedious and repetitive undertaking.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xhQMStRB3cjTCMYMQKQ7QQ.png" /></figure><h3>A Lazy Programmer’s Solution</h3><p>How would a <em>lazy programmer</em> — that is, a programmer who dislikes repeating the same work and therefore builds reusable mechanisms to save effort in the long run — implement SSF?</p><p>Rather than implementing the transmitter endpoints directly in each server, such a programmer would probably build a single backend system responsible for the transmitter functionality and have multiple transmitters rely on it.</p><p>With this approach, the frontend transmitter implementations become much thinner.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l2v5Mrru-t9LGcxbgJCpVw.png" /></figure><blockquote>Younger readers may not be aware of this, but laziness has long been regarded as one of the three great virtues of a programmer.</blockquote><blockquote>Larry Wall wrote in Programming Perl:</blockquote><blockquote>And although we don’t intend to teach you how to program, the perceptive reader will pick up some of the art, and a little of the science, of programming. We will encourage you to develop the three great virtues of a programmer: <strong>laziness</strong>, <strong>impatience</strong>, and <strong>hubris</strong>.</blockquote><p>For example, a backend system of this kind might expose the following APIs corresponding to the transmitter’s Configuration Endpoint, as shown in the figure below.</p><ul><li>/stream/delete</li><li>/stream/read</li><li>/stream/update</li><li>/stream/create</li><li>/stream/replace</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hL7T27HCSgCG_8c9se-Ysg.png" /></figure><h3>Authlete Arena</h3><p>A real-world example of such an SSF backend is <strong>Authlete Arena</strong>, a new product from <a href="https://www.authlete.com/">Authlete</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DtbIyFGC0PuSHzQPJhOphg.png" /></figure><p>A receiver sends a request to a transmitter.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uDNEAcxBecq6lWWZQZYsgw.png" /></figure><p>If the transmitter is implemented using Authlete Arena, it delegates the processing of the incoming request to Authlete Arena. Authlete Arena processes the request and instructs the transmitter on what response should be returned. This delegation model follows Authlete’s <strong>Semi-Hosted Service</strong> pattern (see <a href="https://medium.com/@darutk/new-architecture-of-oauth-2-0-and-openid-connect-implementation-18f408f9338d"><em>New Architecture of OAuth 2.0 and OpenID Connect Implementation</em></a> for more details).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JZ79X_leVZOyCxWi8S--0g.png" /></figure><p>In most cases, Authlete Arena remains hidden behind the transmitter; it never communicates directly with receivers. However, when events are delivered using the push model, Authlete Arena delivers Security Event Tokens directly to the receiver’s endpoint.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ta45YD8ZsZPGkH3t-d3Yug.png" /></figure><p>Authlete Arena’s APIs are protected by OAuth access tokens.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4b3OedXvpUNfvnGAU5SBYg.png" /></figure><p>This implies that an authorization server is required to issue access tokens to transmitters for accessing the Authlete Arena APIs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3lSYj9TA-bIoJie0jviccQ.png" /></figure><p>Authlete Arena communicates with the authorization server to obtain access token information or to retrieve the public keys required to validate access token signatures.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eu4xsOWN9YkEMqbIOsIa8g.png" /></figure><p>Like the Authlete Arena APIs, the transmitter’s endpoints are also protected by OAuth access tokens.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5PVtNh7QwaDfwjYLAX1TTQ.png" /></figure><p>This implies that an authorization server is required to issue access tokens to receivers for accessing the transmitter’s endpoints.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gJOW6lQla4zLO67w7Vy1Ow.png" /></figure><p>Normally, a transmitter would communicate with the authorization server to obtain access token information or to retrieve the public keys required to validate access token signatures. When using Authlete Arena, however, access token validation can be delegated to Authlete Arena.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YIPYYF7Jk33tXJTdIgkpuQ.png" /></figure><p>Instead, Authlete Arena communicates with the authorization server on behalf of the transmitter, either to obtain access token information or to retrieve the public keys required to validate access token signatures.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WBbv6ljR4GAfVziMwHXXOw.png" /></figure><p>The benefit of delegating access token validation to Authlete Arena is that it allows transmitters to leverage Authlete’s expertise in API protection. This expertise covers technologies such as OAuth, <a href="https://www.rfc-editor.org/rfc/rfc8705.html">MTLS</a>, <a href="https://www.rfc-editor.org/rfc/rfc9449.html">DPoP</a>, and <a href="https://www.rfc-editor.org/rfc/rfc9421.html">HTTP Message Signatures</a>. In addition, advanced access-control mechanisms such as “<a href="https://medium.com/@darutk/cedar-policy-in-rar-object-afe8e72e9703">Cedar Policy in RAR Object</a>” are also supported.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2Ydij2KA_Zr-QYSylR6GuA.png" /></figure><p>All functionality provided by Authlete Arena can be controlled through Web APIs. As a result, it can be operated entirely from the command line. That said, a web-based administration console is often more convenient in practice, so Arena Dashboard is planned to provide that capability.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d4EUijMuArMxvHrnJ-hrVQ.png" /></figure><p>The architecture for implementing a transmitter using Authlete Arena is illustrated in the figure below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_wJbSC2XhxVMLG0fxUUy0Q.png" /></figure><h3>A Design Decision</h3><p>Most identity server implementations will probably choose to implement the SSF transmitter endpoints directly within the server itself. In fact, I recently confirmed that the SSF implementation of an open-source project follows exactly this approach, as one would expect.</p><p>After careful consideration, however, Authlete chose a different path: to build a generic SSF backend system that is independent of any particular identity server. As a result, we developed <strong>Authlete Arena</strong> as a completely separate product rather than as an extension to the existing Authlete platform.</p><p>Building a standalone product instead of simply adding new functionality to Authlete required a significant investment of time and effort. In return, however, it enabled us to address virtually any use case in which SSF may be applicable.</p><p>One recent example is the growing interest in using SSF in the context of AI agent governance and control. Because Authlete Arena is designed as a generic SSF backend, we are able to support such use cases as well.</p><h3>Why the Name “Arena” ?</h3><p>The name <strong>Arena</strong> was chosen to emphasize that the system is a generic event delivery mechanism capable of handling not only security-related events, but arbitrary events of any kind.</p><p>In this context, we intentionally use the word <em>arena</em> in its broader sense — a venue for events such as sporting competitions, concerts, and other gatherings. The name reflects the idea that Authlete Arena can serve as a platform for many different kinds of events, not just security events.</p><p>Since the company and product name <strong>Authlete</strong> already evokes the word <em>athlete</em>, the combination <strong>Authlete Arena</strong> felt like a natural fit.</p><h3>Conclusion</h3><p>When we founded Authlete ten years ago, I never imagined that our company would one day become the main sponsor of the OAuth Security Workshop — one of the most technically specialized events in the identity and security industry.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MOCvLITnKCbFhHHCtY9lEQ.png" /></figure><p>Thankfully, Authlete has continued to grow steadily as a profitable company, strengthening its presence both in Japan and around the world.</p><p>We are actively hiring, so if you’re interested in what we do, please feel free to get in touch.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QQtG0ts9z9EiaaRHGhu4vQ.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=846d98076a20" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Beyond OAuth: Authlete, AI Infrastructure, and OSW 2026]]></title>
            <link>https://darutk.medium.com/osw2026-6d7514490867?source=rss-f485feca545e------2</link>
            <guid isPermaLink="false">https://medium.com/p/6d7514490867</guid>
            <category><![CDATA[oauth-security-workshop]]></category>
            <category><![CDATA[oauth]]></category>
            <category><![CDATA[authlete]]></category>
            <dc:creator><![CDATA[Takahiko Kawasaki]]></dc:creator>
            <pubDate>Fri, 15 May 2026 08:14:22 GMT</pubDate>
            <atom:updated>2026-05-15T08:14:22.933Z</atom:updated>
            <content:encoded><![CDATA[<p>From May 27 to 29, 2026, the <a href="https://oauth.secworkshop.events/osw2026">OAuth Security Workshop 2026</a> (OSW 2026) will be held in Leipzig, Germany.</p><p>This year again, <a href="https://www.authlete.com/">Authlete</a> is proud to serve as a Platinum Sponsor. We are also preparing the traditional OSW T-shirt giveaway. This year’s design will feature the <a href="https://www.stadtgeschichtliches-museum-leipzig.de/en/visit/our-museums/monument-of-the-battle-of-the-nations/">Monument to the Battle of the Nations</a> (Völkerschlachtdenkmal), one of Leipzig’s iconic landmarks.</p><p>This will be my first time attending OSW in person in two years. While my primary purpose is, of course, to participate in the event itself, I would be more than happy to talk during breaks about business opportunities, partnerships, and career opportunities at Authlete.</p><p>From Authlete, <a href="https://www.linkedin.com/in/aadnan/">Ali Adnan</a> (Business Development) and <a href="https://www.linkedin.com/in/zuahmed/">Zulfiqar Ahmed</a> (Head of Product) will also be attending in person, so please feel free to reach out to us.</p><h3>Authlete After 10 Years</h3><p>Authlete has now passed its 10th anniversary.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KP9VWkunyT5FEe6q769_JA.jpeg" /><figcaption>Authlete 10th Anniversary Cup Cakes</figcaption></figure><p>We continue to grow profitably while serving excellent customers around the world. The uniqueness of our product architecture has created a strong technical competitive advantage in the market.</p><p>Recently, it feels as though the company is transitioning from the “1 → 10” phase into the “10 → 100” phase. In particular, the increasing number of high-quality customers outside Japan gives us strong confidence in our future growth.</p><p>I believe this is a particularly exciting time to partner with Authlete or to join the company.</p><h3>AI Infrastructure Needs Trust Infrastructure</h3><p>Although Authlete does not position itself as an “AI company,” we have steadily been building technology components related to AI. Because of this, I believe Authlete is becoming a strong partner for the emerging AI ecosystem.</p><p>As AI agents begin operating across organizational and trust boundaries, identity, federation, authorization, and event delivery become increasingly important infrastructure layers.</p><p>At Authlete, we have been investing in these areas through open standards technologies.</p><p>For example, I have already introduced our implementations of <a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/">Client ID Metadata Document</a> and <a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-spiffe-client-auth/">OAuth SPIFFE Client Authentication</a> on LinkedIn. We also natively support <a href="https://openid.net/specs/openid-federation-1_0.html">OpenID Federation</a>, which is one possible approach for bridging AI agents across different trust domains.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FEvOSyqAAAq0%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DEvOSyqAAAq0&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FEvOSyqAAAq0%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/641fdd90994f382b06d6b4e412c76bd5/href">https://medium.com/media/641fdd90994f382b06d6b4e412c76bd5/href</a></iframe><p>In addition, we are currently developing support for the <a href="https://openid.net/specs/openid-sharedsignals-framework-1_0.html">Shared Signals Framework</a> (SSF) as a new product initiative.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LPTQLPRqp2SPnCVkugz77Q.png" /><figcaption>Three Shared Signals Final Specifications</figcaption></figure><p>We view SSF not merely as a framework for security-related notifications, but as a general-purpose event delivery system that can also support coordination among distributed systems and AI agents. This perspective significantly expands the possible applications of SSF beyond traditional security event use cases.</p><p>I hope we will be able to share more details soon.</p><h3>Building a Career in Digital Identity</h3><p>Because of the nature of our business, Authlete works with cutting-edge standards technologies on a daily basis.</p><p>I believe this makes Authlete one of the best places for people who want to build a career in the digital identity industry.</p><p>We also value diversity and actively welcome team members from different countries, languages, and time zones.</p><p>English is our common language, but additional language skills create even more opportunities, especially for people involved in international business development.</p><p>For example, team members who speak Portuguese, Spanish, or Italian actively contribute to communication with existing and prospective customers in their respective regions.</p><h3>See You in Leipzig</h3><p>If you are attending OSW 2026 and are interested in:</p><ul><li>digital identity</li><li>OAuth and OpenID technologies</li><li>AI infrastructure</li><li>federation and trust frameworks</li><li>partnerships with Authlete</li><li>career opportunities at Authlete</li></ul><p>please feel free to reach out.</p><p>Looking forward to seeing you in Leipzig.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6d7514490867" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[DPoP Nonce]]></title>
            <link>https://darutk.medium.com/dpop-nonce-9787b9d276d1?source=rss-f485feca545e------2</link>
            <guid isPermaLink="false">https://medium.com/p/9787b9d276d1</guid>
            <category><![CDATA[security]]></category>
            <category><![CDATA[google-oauth]]></category>
            <category><![CDATA[dpop]]></category>
            <category><![CDATA[api]]></category>
            <dc:creator><![CDATA[Takahiko Kawasaki]]></dc:creator>
            <pubDate>Wed, 14 Jan 2026 08:40:27 GMT</pubDate>
            <atom:updated>2026-01-14T08:40:27.597Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>When sender-constrained access tokens are implemented using <strong>DPoP</strong> (<a href="https://www.rfc-editor.org/rfc/rfc9449.html">RFC 9449</a>), the client includes a <strong>DPoP proof JWT</strong> (<a href="https://www.rfc-editor.org/rfc/rfc9449.html#section-4">Section 4</a>) in requests sent to the server.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lQqk_fz8IY6awMwJpxQIEg.png" /></figure><p>The payload of the DPoP proof JWT must include the following claims:</p><ul><li>jti — A unique identifier for the DPoP proof JWT</li><li>htm — The HTTP method of the request</li><li>htu — The target URI of the request</li><li>iat — The time at which the DPoP proof JWT was generated</li></ul><p>In addition to these, when a DPoP proof JWT is used together with an access token — that is, when the client accesses a protected resource — the payload must also include the following claim:</p><ul><li>ath — The base64url-encoded representation of the SHA-256 hash of the access token</li></ul><p>In addition, if the server requires a nonce, the nonce claim must also be included.</p><ul><li>nonce — The latest nonce provided by the server</li></ul><p>Below is an example of a DPoP proof JWT payload containing a nonce:</p><pre>{<br>  &quot;jti&quot;: &quot;XdPQLwZIH9pmlMhD&quot;,<br>  &quot;htm&quot;: &quot;GET&quot;,<br>  &quot;htu&quot;: &quot;https://rs.example.com/resource/1&quot;,<br>  &quot;iat&quot;: 1755096729,<br>  &quot;ath&quot;: &quot;-7JjHPHFqtqwNHk7dPoU4nL7SgIgLn8vdCiDq-LfovY&quot;,<br>  &quot;nonce&quot;: &quot;ZgmFr7UWLrJX0fHB&quot;<br>}</pre><p>This article takes a closer look at this nonce.</p><h3>Role of the nonce</h3><p><a href="https://www.rfc-editor.org/rfc/rfc9449.html#section-2">Section 2. Objectives</a> of the DPoP specification mentions an attack in which XSS code generates a DPoP proof JWT with a timestamp set in the future and later abuses it elsewhere after some time has passed. To mitigate the impact of such an attack, the server can require that the DPoP proof JWT include time-limited data <strong>provided by the server</strong>, which helps to some extent prevent unauthorized access using pre-generated DPoP proof JWTs.</p><p>Since the DPoP proof JWT is generated by the client, the values of time-related claims in its payload can be chosen freely by the client. Therefore, if the server wants to control the validity period of DPoP proof JWTs, it needs to require that <strong>server-provided</strong>, time-limited data be included in the DPoP proof JWT. The nonce claim is used for this purpose.</p><h3>Provision of the nonce</h3><p>A server that requires a nonce to be included in a DPoP proof JWT returns an error response containing the DPoP-Nonce HTTP header if the DPoP proof JWT does not include a nonce.</p><pre>HTTP/1.1 401 Unauthorized<br>WWW-Authenticate: DPoP error=&quot;use_dpop_nonce&quot;<br>DPoP-Nonce: ZgmFr7UWLrJX0fHB</pre><p>A resource server that complies with <a href="https://www.rfc-editor.org/rfc/rfc6750.html">RFC 6750</a> generates a WWW-Authenticate HTTP header containing the error information, with the error code set to use_dpop_nonce.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0R39UU7YwR9anI-r-o3TYQ.png" /></figure><p>Upon receiving this error, the client extracts the value of the DPoP-Nonce header and creates a DPoP proof JWT that includes that value as the value of the nonce claim.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_ttMl9LvFtALunII_dRwWw.png" /></figure><p>The client then includes the new DPoP proof JWT in the request when resending the resource request.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QuOgDpSXNLon3S0MI4W9Rw.png" /></figure><p>Overall diagram:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6INxko2HMvSJb_X-j2h_BQ.png" /></figure><h3>Detection of nonce expiration</h3><p>The client continues to use the provided nonce for subsequent resource requests. However, that nonce will eventually expire, and when it does, the server returns an error response.</p><p>Upon receiving an error response, if the client can determine that the cause of the error is “nonce expiration,” it will recreate the DPoP proof JWT using the latest nonce included in the error response and resend the request.</p><p>However, determining this is not straightforward. This is because the DPoP specification does not define a dedicated error code (such as dpop_nonce_expired) for this situation.</p><p>Therefore, the client will presume that the error was caused by nonce expiration when the following conditions are met:</p><ul><li>The response status code is “401 Unauthorized”</li><li>The value of the nonce currently held by the client differs from the value of the DPoP-Nonce header included in the error response</li><li>The error code is invalid_dpop_proof</li></ul><pre>HTTP/1.1 401 Unauthorized<br>WWW-Authenticate: DPoP error=&quot;invalid_dpop_proof&quot;<br>DPoP-Nonce: knvXdyXM3FK3QGEK</pre><h3>Request retry logic</h3><p>When using DPoP nonces, it is expected behavior for the server to return error responses periodically, so the client must implement request retry handling to cope with this. As a result, error handling becomes more complex.</p><p>For reference, the pseudocode for request retry is shown below.</p><pre>// Obtain the nonce value currently held by the client. This nonce value is<br>// embedded in the DPoP proof JWT. If this client has never received a nonce<br>// from the server so far, the getDpopNonce() method returns null.<br>String dpopNonce = getDpopNonce();<br><br>// Perform the resource request. (Generation of the DPoP proof JWT is done<br>// inside the invoke() method.)<br>ResponseInfo rinfo = invoke(<br>    request, httpMethod, path, body, mediaType, dpopNonce);<br><br>// If the response contains a DPoP-Nonce HTTP header, update the nonce value<br>// held by this client with the value of that header. If the response does<br>// not include a DPoP-Nonce header, the updateDpopNonceIfSupplied() method<br>// does not update the nonce value and returns null.<br>String updatedDpopNonce = updateDpopNonceIfSupplied(rinfo.getResponse());<br><br>// If the response indicates a DPoP nonce error<br>if (isDpopNonceError(rinfo.getResponse(), dpopNonce, updatedDpopNonce))<br>{<br>    // Create a DPoP proof JWT using the latest nonce value and resend<br>    // the request.<br>    rinfo = invoke(<br>        request, httpMethod, path, body, mediaType, updatedDpopNonce);<br>}<br><br>// The response status code<br>int status = rinfo.getResponse().getStatus();<br><br>// If the request succeeded<br>if (200 &lt;= status &amp;&amp; status &lt; 300)<br>{<br>    return rinfo;<br>}<br><br>// If the request failed<br>throw callError(httpMethod, path, rinfo);</pre><h3>DPoP nonce settings (Authlete-specific)</h3><p><a href="https://www.authlete.com/"><strong>Authlete</strong></a> provides the following configuration options related to DPoP nonces:</p><ul><li>Service.dpopNonceDuration — The validity duration of a DPoP nonce, in seconds</li><li>Service.dpopNonceRequired — Whether the server requires a DPoP nonce</li><li>Client.dpopNonceRequired — Whether a DPoP nonce is required for this client</li></ul><h3>Conclusion</h3><p>A DPoP nonce helps, to some extent, prevent unauthorized access using pre-generated DPoP proof JWTs. In exchange, client-side code becomes somewhat more complex.</p><p>The document <em>“</em><a href="https://www.authlete.com/developers/api_protection/"><strong><em>Comprehensive API Protection with Standard Specifications</em></strong></a><em>”</em> on the Authlete website provides more information about security measures for API protection, including DPoP nonces.</p><p><a href="https://www.rfc-editor.org/rfc/rfc9449.html"><strong>RFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP)</strong></a> is particularly memorable for us, as it is the first IETF RFC whose list of authors includes <a href="https://www.authlete.com/">Authlete</a>. Please visit our <a href="https://authlete.bamboohr.com/careers">Careers</a> page for opportunities to work together with industry experts.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9787b9d276d1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Structure and Implementation of StatusList in the TSL Specification]]></title>
            <link>https://darutk.medium.com/statuslist-93d1c019f9e4?source=rss-f485feca545e------2</link>
            <guid isPermaLink="false">https://medium.com/p/93d1c019f9e4</guid>
            <category><![CDATA[verifiable-credentials]]></category>
            <category><![CDATA[token-status-list]]></category>
            <category><![CDATA[jwt]]></category>
            <category><![CDATA[tsl]]></category>
            <category><![CDATA[cwt]]></category>
            <dc:creator><![CDATA[Takahiko Kawasaki]]></dc:creator>
            <pubDate>Tue, 06 Jan 2026 01:37:53 GMT</pubDate>
            <atom:updated>2026-01-06T01:37:53.921Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>The <a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/"><strong>Token Status List</strong></a> (<strong>TSL</strong>) specification is nearing completion.</p><p>This specification is designed to handle status information (such as valid or invalid) for various token types based on JOSE and COSE, including JWT, SD-JWT VC, CWT, and ISO mdoc.</p><p>To efficiently represent the status of a large number of tokens, statuses are encoded as a bit array and compressed using DEFLATE.</p><p>The TSL specification defines a data structure called StatusList to represent this list of statuses. In this article, we take a closer look at the StatusList.</p><h3>Structure of the StatusList</h3><p>Regardless of whether the format is JSON or CBOR, a StatusList has the following properties:</p><ul><li>bits — REQUIRED. The number of bits used to represent a single status. One of 1, 2, 4, or 8.</li><li>lst — REQUIRED. A DEFLATE-compressed representation of the status values expressed as a byte array. In CBOR, it is represented as a byte array; in JSON, it is represented as a base64url-encoded string.</li><li>aggregation_uri — OPTIONAL. A location from which a list of URIs that publish Status List Tokens can be obtained.</li></ul><p>Below are examples of StatusList objects in JSON format, excerpted from the specification.</p><pre>{<br>  &quot;bits&quot;: 1,<br>  &quot;lst&quot;: &quot;eNrbuRgAAhcBXQ&quot;<br>}</pre><pre>{<br>  &quot;bits&quot;: 2,<br>  &quot;lst&quot;: &quot;eNo76fITAAPfAgc&quot;<br>}</pre><p>The bits property represents the number of bits used to encode a single status, and its possible values are 1, 2, 4, or 8. When the number of bits is 1, up to 2¹ = 2 different statuses can be represented, for example, “valid” and “invalid.” When the number of bits is 2, up to 2² = 4 statuses can be represented; with 4 bits, up to 2⁴ = 16 statuses; and with 8 bits, up to 2⁸ = 256 statuses can be represented.</p><p>The lst property contains the DEFLATE-compressed byte array that enumerates the set of statuses. In the CBOR format, the result of the DEFLATE compression is stored directly as a byte array, whereas in the JSON format, it is base64url-encoded and represented as a string.</p><p>For example, suppose the number of bits used to represent a status is set to 1, and the statuses of 16 tokens are as follows:</p><pre>| Index | Status  | Value |<br>|-------+---------+-------|<br>|   0   | INVALID |   1   |<br>|   1   |  VALID  |   0   |<br>|   2   |  VALID  |   0   |<br>|   3   | INVALID |   1   |<br>|   4   | INVALID |   1   |<br>|   5   | INVALID |   1   |<br>|   6   |  VALID  |   0   |<br>|   7   | INVALID |   1   |<br>|   8   | INVALID |   1   |<br>|   9   | INVALID |   1   |<br>|  10   |  VALID  |   0   |<br>|  11   |  VALID  |   0   |<br>|  12   |  VALID  |   0   |<br>|  13   | INVALID |   1   |<br>|  14   |  VALID  |   0   |<br>|  15   | INVALID |   1   |</pre><p>If these are packed as shown in the following figure, the resulting byte array is [0xB9, 0xA3].</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VoDqC0-YR89GiiLofpzQlA.png" /><figcaption>Statuses represented as a byte array with bits=1</figcaption></figure><p>This byte array is then compressed using DEFLATE and set as the value of lst.</p><p>Similarly, suppose the number of bits used to represent a status is 2, and the statuses of 12 tokens are as follows:</p><pre>| Index | Status |<br>|-------+--------|<br>|   0   |   01   |<br>|   1   |   10   |<br>|   2   |   00   |<br>|   3   |   11   |<br>|   4   |   00   |<br>|   5   |   01   |<br>|   6   |   00   |<br>|   7   |   01   |<br>|   8   |   01   |<br>|   9   |   10   |<br>|  10   |   11   |<br>|  11   |   11   |</pre><p>When these statuses are packed as illustrated in the following figure, the resulting byte array is [0xC9, 0x44, 0xF9].</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u6JuX8-t3RtNTt3b7uNChg.png" /><figcaption>Statuses represented as a byte array with bits=2</figcaption></figure><p>This byte array is then compressed using DEFLATE and set as the value of lst.</p><h3>Implementation of the StatusList</h3><p>The CBOR library <a href="https://github.com/authlete/cbor/"><strong>authlete/cbor</strong></a> (for Java), which <a href="https://www.authlete.com/"><strong>Authlete</strong></a> publishes as open source, includes a class named StatusList for representing a StatusList in the com.authlete.cbor.tsl package starting from version 1.21.</p><p>The constructor of this StatusList class takes the following arguments: int bits, byte[] lst, and String aggregationUri.</p><pre>public StatusList(int bits, byte[] lst, String aggregationUri)</pre><p>However, preparing the value of lst in the first place is cumbersome, so the path to calling this constructor is not so easy. To address this, a utility class named StatusListBuilder is provided for creating instances of the StatusList class.</p><p>The usage of StatusListBuilder is as follows:</p><ol><li>Choose the number of bits used to represent each status from among 1, 2, 4, or 8.</li><li>Create a StatusListBuilder instance by passing the chosen bit size to its constructor.</li><li>Call the valueAt method as many times as needed to set individual status values.</li><li>If you want to set aggregation_uri, call the aggregationUri method.</li><li>If you want to reserve capacity beyond the number of statuses automatically expanded by the indices specified via valueAt, call the capacity method.</li><li>Finally, call the build method to create a StatusList instance.</li></ol><p>For example, to create a StatusList that contains the following set of statuses listed in Section 4.1. Compressed Byte Array of the TSL specification,</p><pre>status[ 0] = 0b1<br>status[ 1] = 0b0<br>status[ 2] = 0b0<br>status[ 3] = 0b1<br>status[ 4] = 0b1<br>status[ 5] = 0b1<br>status[ 6] = 0b0<br>status[ 7] = 0b1<br>status[ 8] = 0b1<br>status[ 9] = 0b1<br>status[10] = 0b0<br>status[11] = 0b0<br>status[12] = 0b0<br>status[13] = 0b1<br>status[14] = 0b0<br>status[15] = 0b1</pre><p>you can write the code as follows:</p><pre>// Number of bits used to represent a status<br>int bits = 1;<br><br>// Create a StatusListBuilder instance by passing the number<br>// of bits to the constructor<br>StatusListBuilder builder = new StatusListBuilder(bits);<br><br>// Set status values<br>builder.valueAt(StatusTypeValue.INVALID,  0);<br>builder.valueAt(StatusTypeValue.VALID,    1);<br>builder.valueAt(StatusTypeValue.VALID,    2);<br>builder.valueAt(StatusTypeValue.INVALID,  3);<br>builder.valueAt(StatusTypeValue.INVALID,  4);<br>builder.valueAt(StatusTypeValue.INVALID,  5);<br>builder.valueAt(StatusTypeValue.VALID,    6);<br>builder.valueAt(StatusTypeValue.INVALID,  7);<br>builder.valueAt(StatusTypeValue.INVALID,  8);<br>builder.valueAt(StatusTypeValue.INVALID,  9);<br>builder.valueAt(StatusTypeValue.VALID,   10);<br>builder.valueAt(StatusTypeValue.VALID,   11);<br>builder.valueAt(StatusTypeValue.VALID,   12);<br>builder.valueAt(StatusTypeValue.INVALID, 13);<br>builder.valueAt(StatusTypeValue.VALID,   14);<br>builder.valueAt(StatusTypeValue.INVALID, 15);<br><br>// Create a StatusList instance<br>StatusList statusList = builder.build();</pre><p>StatusList is a subclass of CBORItem, so you can use the encodeToHex() method to represent it in hexadecimal,</p><pre>System.out.println(statusList.encodeToHex());</pre><pre>a2646269747301636c73744a78dadbb918000217015d</pre><p>or use the toString() or prettify() methods to represent it in CBOR Diagnostic Notation.</p><pre>System.out.println(statusList.prettify());</pre><pre>{<br>  &quot;bits&quot;: 1,<br>  &quot;lst&quot;: h&#39;78dadbb918000217015d&#39;<br>}</pre><p>By the way, if you convert the byte array to a base64url string using the Base64UrlAdapter introduced in <em>“</em><a href="https://darutk.medium.com/gson-base64url-conversion-3a5fd61de048"><em>GSON: Converting Byte Arrays to and from Base64URL Strings</em></a><em>”</em> (or by any other method—what matters is simply converting the lst value from a byte array to a base64url string),</p><pre>// Create a Gson instance with Base64UrlAdapter enabled<br>Gson gson = new GsonBuilder()<br>        .registerTypeAdapter(byte[].class, new Base64UrlAdapter())<br>        .setPrettyPrinting()<br>        .create();<br><br>// Convert the CBORItem into a general-purpose Java object using<br>// the parse() method<br>Object statusListObject = statusList.parse();<br><br>// Convert to JSON<br>String json = gson.toJson(statusListObject);<br><br>// Output the JSON<br>System.out.println(json);</pre><p>you can generate JSON that complies with the requirements defined in Section 4.2. Status List in JSON Format of the TSL specification.</p><pre>{<br>  &quot;bits&quot;: 1,<br>  &quot;lst&quot;: &quot;eNrbuRgAAhcBXQ&quot;<br>}</pre><h3>Conclusion</h3><p>When you need to work with CBOR in Java, please give the <a href="https://github.com/authlete/cbor/"><strong>authlete/cbor</strong></a> library a try. <a href="https://www.authlete.com/">Authlete</a> itself also uses the authlete/cbor library in its implementation of <a href="https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html">OpenID for Verifiable Credential Issuance 1.0</a> (OID4VCI).</p><p>We also operate a website called <a href="https://cbor.zone/"><strong>CBOR Zone</strong></a>, which allows you to convert CBOR data between the following formats. It’s quite handy, so be sure to check it out!</p><ul><li>Hexadecimal</li><li>Base64</li><li>Base64URL</li><li>Diagnostic Notation</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kg_v6NGluZHEMQTgkfOckA.png" /><figcaption>CBOR Zone (https://cbor.zone/)</figcaption></figure><p>(This screenshot shows the conversion from the hexadecimal representation of a StatusList to Diagnostic Notation.)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=93d1c019f9e4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GSON: Converting Byte Arrays to and from Base64URL Strings]]></title>
            <link>https://darutk.medium.com/gson-base64url-conversion-3a5fd61de048?source=rss-f485feca545e------2</link>
            <guid isPermaLink="false">https://medium.com/p/3a5fd61de048</guid>
            <category><![CDATA[json]]></category>
            <category><![CDATA[base64url]]></category>
            <category><![CDATA[songs]]></category>
            <dc:creator><![CDATA[Takahiko Kawasaki]]></dc:creator>
            <pubDate>Tue, 06 Jan 2026 00:11:29 GMT</pubDate>
            <atom:updated>2026-01-06T00:11:29.700Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>With <a href="https://github.com/google/gson">GSON</a>’s default behavior, serializing a byte array results in an array of numbers.</p><pre>// Create a Map instance representing the following data structure:<br>//<br>//   {<br>//     &quot;binary&quot;: [ 18, 52, 86 ]<br>//   }<br>//<br>Map&lt;String, Object&gt; input = new LinkedHashMap&lt;&gt;();<br>input.put(&quot;binary&quot;, { (byte)0x12, (byte)0x34, (byte)0x56 };<br><br>// Prepare a Gson instance.<br>Gson gson = new GsonBuilder().setPrettyPrinting().create();<br><br>// Convert the data to JSON.<br>String json = gson.toJson(input);<br><br>// Print the JSON.<br>System.out.println(json);</pre><p>Output produced by the code above:</p><pre>{<br>  &quot;binary&quot;: [<br>    18,<br>    52,<br>    86<br>  ]<br>}</pre><p>However, in some cases, you may want to serialize a byte array as a base64url-encoded string, as shown below.</p><pre>{<br>  &quot;binary&quot;: &quot;EjRW&quot;<br>}</pre><p>This article explains how to configure GSON to perform bidirectional conversion between byte arrays and base64url-encoded strings.</p><h3>TypeAdapter</h3><p>If you want to customize the conversion logic for a specific type, you create a subclass of TypeAdapter and register it with GsonBuilder.</p><p>The TypeAdapter class has two abstract methods that must be implemented. One is the write method, which is called during serialization, and the other is the read method, which is called during deserialization.</p><p>In the context of this blog post, the write method converts the given byte array into a base64url-encoded string and writes it out, while the read method reads a base64url-encoded string, converts it into a byte array, and returns it.</p><p>An implementation of this logic looks like the following:</p><pre>package com.example;<br><br><br>import java.io.IOException;<br>import java.util.Base64;<br>import com.google.gson.TypeAdapter;<br>import com.google.gson.stream.JsonReader;<br>import com.google.gson.stream.JsonToken;<br>import com.google.gson.stream.JsonWriter;<br><br><br>public class Base64UrlAdapter extends TypeAdapter&lt;byte[]&gt;<br>{<br>    @Override<br>    public void write(JsonWriter out, byte[] value) throws IOException<br>    {<br>        if (value == null)<br>        {<br>            // Write null.<br>            out.nullValue();<br>        }<br>        else<br>        {<br>            // Conver the byte array into a base64url-encoded string<br>            // and write it.<br>            out.value(toBase64Url(value));<br>        }<br>    }<br><br><br>    @Override<br>    public byte[] read(JsonReader in) throws IOException<br>    {<br>        if (in.peek() == JsonToken.NULL)<br>        {<br>            // Read null and return null.<br>            in.nextNull();<br>            return null;<br>        }<br><br>        // Read a base64url-encoded string and convert it to a byte array.<br>        return fromBase64Url(in.nextString());<br>    }<br><br><br>    private static String toBase64Url(byte[] value)<br>    {<br>        // Convert the byte array into a base64url-encoded string.<br>        return Base64.getUrlEncoder().withoutPadding().encodeToString(value);<br>    }<br><br><br>    private static byte[] fromBase64Url(String value)<br>    {<br>        // Convert the base64url-encoded string into a byte array.<br>        return Base64.getUrlDecoder().decode(value);<br>    }<br>}</pre><p>Once you register the Base64UrlAdapter implemented above with GsonBuilder using the registerTypeAdapter method and then create a Gson instance, the setup is complete.</p><pre>Gson gson = new GsonBuilder()<br>        .registerTypeAdapter(byte[].class, new Base64UrlAdapter())<br>        .create();</pre><h3>Code Examples</h3><p>The following are code examples for serializing a byte array to a base64url string and for deserializing a base64url string back into a byte array.</p><pre>@Test<br>public void test_to_base64url()<br>{<br>    // Create a Gson instance with Base64UrlAdapter enabled.<br>    Gson gson = new GsonBuilder()<br>            .registerTypeAdapter(byte[].class, new Base64UrlAdapter())<br>            .create();<br><br>    // Create a Map instance representing the following data structure:<br>    //<br>    //   {<br>    //     &quot;binary&quot;: [ 18, 52, 86 ]<br>    //   }<br>    //<br>    Map&lt;String, Object&gt; input = new LinkedHashMap&lt;&gt;();<br>    input.put(&quot;binary&quot;, new byte[]{ (byte)0x12, (byte)0x34, (byte)0x56 });<br><br>    // Convert the data to JSON.<br>    String json = gson.toJson(input);<br><br>    // The JSON content should be {&quot;binary&quot;:&quot;EjRW&quot;}<br>    assertEquals(json, &quot;{\&quot;binary\&quot;:\&quot;EjRW\&quot;}&quot;);<br>}</pre><pre>@Test<br>public void test_from_base64url()<br>{<br>    // Create a Gson instance with Base64UrlAdapter enabled.<br>    Gson gson = new GsonBuilder()<br>            .registerTypeAdapter(byte[].class, new Base64UrlAdapter())<br>            .create();<br><br>    // Prepare a JSON string with the content {&quot;binary&quot;:&quot;EjRW&quot;}<br>    String input = &quot;{\&quot;binary\&quot;:\&quot;EjRW\&quot;}&quot;;<br><br>    // Deserialize the JSON and obtain an instance of the Data class.<br>    Data data = gson.fromJson(input, Data.class);<br><br>    // Verify that the byte array data.binary has length 3 and that<br>    // its elements are (byte)0x12, (byte)0x34, and (byte)0x56 in order.<br>    assertNotNull(data.binary);<br>    assertEquals(3, data.binary.length);<br>    assertEquals((byte)0x12, data.binary[0]);<br>    assertEquals((byte)0x34, data.binary[1]);<br>    assertEquals((byte)0x56, data.binary[2]);<br>}<br><br><br>private static class Data<br>{<br>    byte[] binary;<br>}</pre><h3>Conclusion</h3><p>In the <a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/">Token Status List</a> specification, the value of the lst property in the StatusList structure is a byte array in the CBOR format, but a base64url-encoded string in the JSON format. When dealing with such data structures, the Base64UrlAdapter created in this article may be useful.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3a5fd61de048" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cedar Policy in RAR Object]]></title>
            <link>https://darutk.medium.com/cedar-policy-in-rar-object-afe8e72e9703?source=rss-f485feca545e------2</link>
            <guid isPermaLink="false">https://medium.com/p/afe8e72e9703</guid>
            <category><![CDATA[oauth]]></category>
            <category><![CDATA[access-token]]></category>
            <category><![CDATA[rar]]></category>
            <category><![CDATA[cedar-policies]]></category>
            <dc:creator><![CDATA[Takahiko Kawasaki]]></dc:creator>
            <pubDate>Mon, 29 Dec 2025 14:38:31 GMT</pubDate>
            <atom:updated>2025-12-29T22:00:29.157Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>There is an authorization policy language called <a href="https://docs.cedarpolicy.com/"><strong>Cedar Policy Language</strong></a>. Policies written in this language are called <strong>Cedar Policies</strong>. In this article, we look at how to associate Cedar Policies with access tokens and use them for fine-grained access control.</p><h3>Advantages of Cedar Policy</h3><p>One of the major advantages of Cedar Policy is that it allows <strong>authorization logic to be separated from business logic</strong>. Cedar Policies can be externalized and managed independently from application code. In fact, there are commercial services available that manage Cedar Policies (see <a href="https://www.cedarpolicy.com/en/integrations"><em>Integrations</em></a>).</p><p>Another major advantage of Cedar Policy is that it enables <strong>authorization logic to be expressed with fine-grained control</strong>. For example, it is possible to describe an authorization rule such as: “If a user is a friend of Jane, they are allowed to view and comment on photos in Jane’s travel album.” Below is an example of a Cedar Policy excerpted from the <a href="https://docs.cedarpolicy.com/overview/scenario.html"><em>Example Scenario</em></a> in the <a href="https://docs.cedarpolicy.com/">Cedar Policy Language Reference Guide</a> that expresses this authorization logic.</p><pre>permit (<br>    principal in Group::&quot;janeFriends&quot;,<br>    action in [Action::&quot;view&quot;, Action::&quot;comment&quot;],<br>    resource in Album::&quot;janeTrips&quot;<br>);</pre><p>In Cedar Policy, you can add when and unless clauses to specify the conditions under which a policy applies. Within these conditions, you can use comparison operators, logical operators, arithmetic operators, and more, providing a high level of expressiveness.</p><pre>permit (<br>    principal,<br>    action == Action::&quot;read&quot;,<br>    resource<br>)<br>when {<br>    resource.owner == principal ||<br>    resource.tag == &quot;public&quot;<br>};</pre><h3>Cedar Policy in RAR Object</h3><p>By associating scopes (<a href="https://www.rfc-editor.org/rfc/rfc6749.html#section-3.3">RFC 6749, Section 3.3</a>) with an access token, it is possible to express coarse-grained permissions for that access token. In a similar way, if Cedar Policies could be associated with an access token, it should be possible to express fine-grained permissions for that access token.</p><p>One promising approach to realizing this idea is to represent Cedar Policies as RAR objects (<a href="https://www.rfc-editor.org/rfc/rfc9396.html">RFC 9396</a>).</p><p>✏️ <em>NOTE: The OAuth 2.0 Rich Authorization Requests specification (RFC 9396), commonly known as RAR, does not use the term “RAR object.” However, at an event, one of the authors of the specification referred to each element in the </em><em>authorization_details array that way. I found the term convenient, so I use “RAR object” in the following discussion as well.</em></p><p>The idea of representing Cedar Policies as RAR objects has been discussed in the past, for example in the IETF individual draft <a href="https://datatracker.ietf.org/doc/draft-cecchetti-oauth-rar-cedar/"><em>draft-cecchetti-oauth-rar-cedar</em></a> (now expired) and in forums such as the OAuth Working Group at IETF 119.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FOh0EaisUtYI%3Fstart%3D1829%26feature%3Doembed%26start%3D1829&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DOh0EaisUtYI&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FOh0EaisUtYI%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/6bebacfbeb0dd0169e39fb74d8faf0d0/href">https://medium.com/media/6bebacfbeb0dd0169e39fb74d8faf0d0/href</a></iframe><p>However, after reviewing the earlier discussions, I could not find a design that fully met my needs. Therefore, I decided to design one myself. In the next section, I will describe the considerations that guided this design.</p><h3><strong>Designing Cedar Policy as a RAR Object</strong></h3><h4><strong>Choosing a Notation for Cedar Policies</strong></h4><p>The original notation of Cedar Policy is distinctive. Whether this notation should be embedded into a RAR object as-is, or first converted into JSON (<a href="https://docs.cedarpolicy.com/policies/json-format.html">JSON Policy Format</a>) and then embedded, is a point on which opinions may differ.</p><p>While the JSON format has good affinity with RAR, it is verbose — especially in the representation of when and unless conditions—and many in the Cedar community are likely to prefer the original notation. For this reason, standardizing on only one notation could cause issues later. In such cases, it is safer to design the specification so that both notations are supported.</p><p>This leads to a further design decision: whether the notation should be specified using the mandatory type property of a RAR object (<a href="https://www.rfc-editor.org/rfc/rfc9396.html#section-2">RFC 9396, Section 2</a>), or whether a new property (for example, format) should be introduced.</p><p>If the type property were used, its value would need to convey two meanings at once—namely, that the object represents a Cedar Policy and which notation is used—resulting in values such as cedar-policy-native or cedar-policy-json.</p><p>At first glance, overloading a single property with two distinct meanings seems like a poor design choice. Moreover, it raises the question of whether Cedar Policy is the only Cedar-related artifact that might need to be represented in RAR. It is conceivable that Cedar Policies and Cedar Policy Templates might need to be treated as distinct entities. There is also the possibility that Cedar Schemas could be represented as RAR objects in the future. In such cases, the type property would be better suited for distinguishing among these artifacts.</p><p>In conclusion, a format property is introduced, whose value is either cedar or json, allowing the original notation or the JSON notation to be selected. Furthermore, based on the expectation that more users will prefer the original notation, cedar is defined as the default value when the format property is omitted.</p><h4>type Property</h4><p>In a RAR object, the type property is mandatory (<a href="https://www.rfc-editor.org/rfc/rfc9396.html#section-2">RFC 9396, Section 2</a>). It has been decided that the value of this property will indicate that the content of the RAR object is a Cedar Policy, but the concrete value must still be defined.</p><p>The first candidate that comes to mind is the string cedar. However, as mentioned in the previous section, choosing cedar would likely cause problems if there is a future need to distinguish among Cedar Policy, Cedar Policy Template, and Cedar Schema.</p><p>Taking these considerations into account, it seems appropriate to use cedar-policy as the value of the type property to indicate that the object represents a Cedar Policy.</p><h4>Cedar Policy Set</h4><p>There is also a design choice between representing a single Cedar Policy in one RAR object or representing a Cedar Policy Set.</p><p>In <a href="https://docs.cedarpolicy.com/policies/json-format.html#policy-set-format"><em>Representing a Policy Set with JSON</em></a>, the JSON representation of a Cedar Policy Set contains staticPolicies, templates, and templateLinks within a single JSON object. In principle, it would be possible to embed equivalent information into a single RAR object.</p><p>However, I could not shake the concern that, in the context of RAR, support for templates might not be necessary in the first place. When a Cedar Policy is embedded statically in a RAR object, it seems likely that any placeholders in templates would already be resolved before embedding.</p><p>That said, the decision was not straightforward, so I chose to defer it. For the time being, a Cedar Policy Set is represented using multiple RAR objects within the authorization_details array. If operational experience later reveals issues with this approach, the mapping of a Cedar Policy Set to RAR objects can be revisited and redesigned at that point.</p><h4>principal, action, resource</h4><p>Within a Cedar Policy, the principal, action, and resource are referenced. What values should these have?</p><p>A Cedar Policy embedded in a RAR object is evaluated when the access token to which that RAR object is attached is used to access a resource. Therefore, the values of principal, action, and resource should align with this context.</p><p>For principal, it is natural to use the subject associated with the access token (a typical example being a user ID). Following common Cedar Policy examples, using User as the entity type would be appropriate. With this approach, a condition such as “the principal is the user user001” can be expressed as follows.</p><pre>principal == User::&quot;user001&quot;</pre><p>However, access tokens issued via the Client Credentials flow (<a href="https://www.rfc-editor.org/rfc/rfc6749.html#section-4.4">RFC 6749, Section 4.4</a>) are not associated with a user. In such cases, the entity type should be Client.</p><pre>principal == Client::&quot;app001&quot;</pre><p>✏️ <em>NOTE: At this point, I am not yet certain whether it is a good idea to distinguish between cases where an access token is associated with a subject and cases where it is not.</em></p><p>If an access token contains groups, roles, or entitlements attributes (<a href="https://www.rfc-editor.org/rfc/rfc9068.html#section-2.2.3">RFC 9068, Section 2.2.3</a>), it would be reasonable to map each of them to Group, Role, and Entitlement entities, respectively, and set them as parent entities of the principal. This would allow the use of the in operator within Cedar Policies.</p><pre>principal in Group::&quot;group1&quot;</pre><p>The value of principal is determined by the authorization server at the time the access token is issued. In contrast, action and resource belong to the resource server, and it is preferable to let the resource server define their concrete values.</p><p>For example, a server implementing the <a href="https://openid.net/specs/openid-sharedsignals-framework-1_0-final.html">Shared Signals Framework</a> might define the action of creating a stream as SSF::Stream::Action::&quot;create&quot;. In that case, a Cedar Policy could include a statement such as the following.</p><pre>action == SSF::Stream::Action::&quot;create&quot;</pre><h4>context</h4><p>I believe that, as variables available in the context referenced from when and unless clauses in a Cedar Policy, at least the following can be provided in any environment.</p><ul><li>time — The current time expressed as milliseconds elapsed since the Unix epoch.</li><li>access_token — Information about the access token. It has sub-properties such as iss, sub, and client_id.</li><li>ip_address — The client’s IP address.</li></ul><h4>Design Summary</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bDdMadz99ztLX3Tj3AUMjg.png" /></figure><p><strong>Properties</strong></p><ul><li>type — REQUIRED. The value is fixed to cedar-policy.</li><li>format — OPTIONAL. cedar (default) or json.</li><li>policy_id — OPTIONAL. Policy ID.</li><li>policy — REQUIRED. The Cedar Policy. If the value of the format property is cedar, this is a single string representing a Cedar Policy written in the native Cedar syntax. If the value of the format property is json, this is a JSON object representing a Cedar Policy written in the <a href="https://docs.cedarpolicy.com/policies/json-format.html">JSON Policy Format</a>.</li></ul><p><strong>Entities</strong></p><ul><li>principal — If an access token is associated with a subject (i.e., the introspection response or the payload of a JWT access token contains a sub claim), the entity of principal is User. Otherwise, it is Client. For a User entity, the value of the sub claim in the access token is treated as the unique identifier. For a Client entity, the value of the client_id in the access token is treated as the unique identifier. Each element of groups, roles, and entitlements in the access token (<a href="https://www.rfc-editor.org/rfc/rfc9068.html#section-2.2.3">RFC 9068, Section 2.2.3</a>) is mapped to a Group, Role, or Entitlement entity, respectively, and is set as a parent entity of the principal. If the token follows <a href="https://www.rfc-editor.org/rfc/rfc7643.html#section-2.4">RFC 7643, Section 2.4</a>, each element is expected to be a JSON object with a value property (e.g., &quot;groups&quot;:[{&quot;value&quot;:&quot;group1&quot;}]), and the value of that value property is treated as the unique identifier of the entity. If it does not follow RFC 7643, Section 2.4, the element is assumed to be a JSON string (e.g., &quot;groups&quot;:[&quot;group1&quot;]), and the string value itself is treated as the unique identifier of the entity.</li><li>action — The entity of action is defined by the resource server.</li><li>resource — The entity of resource is defined by the resource server.</li></ul><p><strong>Context</strong></p><ul><li>time — The current time expressed as milliseconds elapsed since the Unix epoch.</li><li>access_token — Information about the access token. It has sub-properties such as iss, sub, and client_id.</li><li>ip_address — The client’s IP address.</li></ul><h3>Examples of Cedar Policies in RAR Objects</h3><p>Based on the design described above, examples of Cedar Policies in RAR objects are as follows.</p><pre>[<br>  {<br>    &quot;type&quot;:      &quot;cedar-policy&quot;,<br>    &quot;format&quot;:    &quot;cedar&quot;,<br>    &quot;policy_id&quot;: &quot;policy-0&quot;,<br>    &quot;policy&quot;:    &quot;permit (principal == User::\&quot;12UA45\&quot;, action == Action::\&quot;view\&quot;, resource);&quot; <br>  },<br>  {<br>    &quot;type&quot;:      &quot;cedar-policy&quot;,<br>    &quot;format&quot;:    &quot;json&quot;,<br>    &quot;policy_id&quot;: &quot;policy-1&quot;,<br>    &quot;policy&quot;: {<br>      &quot;effect&quot;:  &quot;permit&quot;,<br>      &quot;principal&quot;: { &quot;op&quot;: &quot;==&quot;, &quot;entity&quot;: { &quot;type&quot;: &quot;User&quot;,   &quot;id&quot;: &quot;12UA45&quot; } },<br>      &quot;action&quot;:    { &quot;op&quot;: &quot;==&quot;, &quot;entity&quot;: { &quot;type&quot;: &quot;Action&quot;, &quot;id&quot;: &quot;view&quot;   } },<br>      &quot;resource&quot;:  { &quot;op&quot;: &quot;All&quot; }<br>    }<br>  }<br>]</pre><p>This example contains two RAR objects. The first describes a Cedar Policy using the original Cedar syntax, and the second describes the Cedar Policy using the JSON format. However, both represent exactly the same policy semantics.</p><h3>Cedar Policies for Admin-Issued Access Tokens</h3><p>In standardized authorization flows, an access token is issued as a result of the client requesting it. Therefore, in those flows, the client specifies the set of RAR objects to be associated with the access token by using the authorization_details request parameter. However, in real-world commercial systems, it is quite common to issue access tokens without using standardized flows—that is, in so-called out-of-band ways.</p><p>If one assumes only scenarios in which the client specifies RARs, embedding Cedar Policies in RARs may seem unnatural. In other words, it may feel odd for a client to request complex Cedar Policies on its own. However, when considering cases where a server administrator manually creates an access token and hands it to a client application, the ability to embed Cedar Policies in RAR objects becomes useful.</p><p>For example, when a server administrator issues an access token for demonstration purposes, they might want to grant broad permissions while preventing accidental deletion of a demo stream. In such a case, the administrator could associate the following Cedar Policies with the access token.</p><pre>[<br>  {<br>   &quot;type&quot;: &quot;cedar-policy&quot;,<br>    &quot;policy&quot;: &quot;permit(principal,action,resource);&quot;<br>  },<br>  {<br>    &quot;type&quot;: &quot;cedar-policy&quot;,<br>    &quot;policy&quot;: &quot;forbid(principal,action==SSF::Stream::Action::\&quot;delete\&quot;,resource==SSF::Stream::\&quot;stj3odpcfg19u6q62t2h7k0uk3t7srkdai6e0ccd7jd4ognt\&quot;);&quot;<br>  }<br>]</pre><p>In this example, executing the SSF::Stream::Action::&quot;delete&quot; action on the resource SSF::Stream::&quot;stj3odpcfg19u6q62t2h7k0uk3t7srkdai6e0ccd7jd4ognt&quot;—a <a href="https://openid.net/specs/openid-sharedsignals-framework-1_0.html">Shared Signals Framework</a> stream prepared for demonstration purposes—is forbidden.</p><h3>Demo Videos</h3><p>Cedar Policy in RAR Object is not merely a theoretical idea; it has been implemented in practice and demonstrated to work.</p><h4>In a LinkedIn Post</h4><ul><li><a href="https://www.linkedin.com/feed/update/urn:li:activity:7348451868018122752/">https://www.linkedin.com/feed/update/urn:li:activity:7348451868018122752/</a></li></ul><h4>In an online study session (in Japanese)</h4><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F00h63xwX2pY%3Fstart%3D1567%26feature%3Doembed%26start%3D1567&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D00h63xwX2pY&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F00h63xwX2pY%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/3d0aa5bf9d50dc16481d034eeab30f23/href">https://medium.com/media/3d0aa5bf9d50dc16481d034eeab30f23/href</a></iframe><h3>Conclusion</h3><p>Personally, I believe the question of whether Cedar Policy in RAR Object is useful has already been answered. Its usefulness has been demonstrated in a new product currently under development at our company, and a certain public-sector organization has also shown strong interest in it as a means of addressing challenges in a national strategic project.</p><p>It would be great if a specification for Cedar Policy in RAR Object were standardized, but I personally am too busy to devote time to the standardization discussions. Hopefully, it will be standardized in a form close to the implementation presented in this article.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=afe8e72e9703" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Verification of HTTP Message Signatures]]></title>
            <link>https://darutk.medium.com/verification-of-http-message-signatures-501bbdc7dfec?source=rss-f485feca545e------2</link>
            <guid isPermaLink="false">https://medium.com/p/501bbdc7dfec</guid>
            <category><![CDATA[signature]]></category>
            <category><![CDATA[http-message-signatures]]></category>
            <category><![CDATA[https]]></category>
            <dc:creator><![CDATA[Takahiko Kawasaki]]></dc:creator>
            <pubDate>Mon, 15 Dec 2025 23:51:47 GMT</pubDate>
            <atom:updated>2025-12-15T23:51:47.784Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>I have published an open-source Java library, <a href="https://github.com/authlete/http-message-signatures"><strong>authlete/http-message-signatures</strong></a>, which implements the <a href="https://www.rfc-editor.org/rfc/rfc9421.html">RFC 9421 HTTP Message Signatures</a> specification. Last month, I received feedback pointing out that the library lacked a test demonstrating the procedure for reconstructing the signature base for signature verification (<a href="https://github.com/authlete/http-message-signatures/issues/1">Issue 1</a>).</p><p>Due to various circumstances, the writing of detailed documentation has been postponed, so at present, understanding how to use the library requires reading the implementation and test code. To address this, I added a test case, <a href="https://github.com/authlete/http-message-signatures/blob/main/src/test/java/com/authlete/hms/RFC9421Test.java">RFC9421Test.java</a>, which verifies an HTTP message signature based on the examples provided in the Appendix of RFC 9421.</p><p>In this article, we walk through the procedure for verifying HTTP message signatures, following that test code step by step.</p><p>🗒️ <em>NOTE: For an overview of RFC 9421, please refer to the section “HTTP Message Signature” in the document “</em><a href="https://www.authlete.com/developers/api_protection/"><em>Comprehensive API Protection with Standard Specifications</em></a><em>” available on the </em><a href="https://www.authlete.com/"><em>Authlete</em></a><em> website.</em></p><h3>HTTP Message Signature Verification Procedure</h3><h4>Parse the Signature HTTP Field</h4><p>A signed HTTP message includes a Signature HTTP field. RFC 9421, section <a href="https://www.rfc-editor.org/rfc/rfc9421.html#appendix-B.2.4">B.2.4 “Signing a Response Using ecdsa-p256-sha256”</a>, provides the following example.</p><pre>Signature: sig-b24=:wNmSUAhwb5LxtOtOpNa6W5xj067m5hFrj0XQ4fvpaCLx0NK\<br>  ocgPquLgyahnzDnDAUy5eCdlYUEkLIj+32oiasw==:</pre><p>The value of the Signature HTTP field is formatted as a Dictionary (<a href="https://www.rfc-editor.org/rfc/rfc8941.html#section-3.2">RFC 8941, Section 3.2</a>). Each individual key–value pair consists of an arbitrary label and a signature represented as a Byte Sequence (<a href="https://www.rfc-editor.org/rfc/rfc8941.html#section-3.3.5">RFC 8941, Section 3.3.5</a>).</p><pre>Signature: <em>label</em>=:<em>base64-encoded-signature</em>:, <em>label</em>=:<em>base64-encoded-signature</em>:, ...</pre><p>The http-message-signatures library provides a utility class called SignatureField that represents the Signature HTTP field, so you can use it to parse the field.</p><pre>// The value of the Signature HTTP field<br>String signatureFieldValue =<br>        &quot;sig-b24=:wNmSUAhwb5LxtOtOpNa6W5xj067m5hFrj0XQ4fvpaCLx0NK&quot; +<br>        &quot;ocgPquLgyahnzDnDAUy5eCdlYUEkLIj+32oiasw==:&quot;;<br><br>// Parse the value of the Signature HTTP field<br>SignatureField signatureField =<br>        SignatureField.parse(signatureFieldValue);</pre><h4>Parse the Signature-Input HTTP Field</h4><p>A signed HTTP message also includes a Signature-Input HTTP field. RFC 9421, section <a href="https://www.rfc-editor.org/rfc/rfc9421.html#appendix-B.2.4">B.2.4 “Signing a Response Using ecdsa-p256-sha256”</a>, provides the following example.</p><pre>Signature-Input: sig-b24=(&quot;@status&quot; &quot;content-type&quot; \<br>  &quot;content-digest&quot; &quot;content-length&quot;);created=1618884473\<br>  ;keyid=&quot;test-key-ecc-p256&quot;</pre><p>The value of the Signature-Input HTTP field is also formatted as a Dictionary (<a href="https://www.rfc-editor.org/rfc/rfc8941.html#section-3.2">RFC 8941, Section 3.2</a>). Each individual key–value pair consists of an arbitrary label and metadata represented as an Inner List (<a href="https://www.rfc-editor.org/rfc/rfc8941.html#section-3.1.1">RFC 8941, Section 3.1.1</a>).</p><pre>Signature-Input: <em>label</em>=(<em>component identifiers</em>)<em>optional parameters</em>, <em>label</em>=(<em>component identifiers</em>)<em>optional parameters</em>, ...</pre><p>The http-message-signatures library provides a utility class called SignatureInputField that represents the Signature-Input HTTP field, so you can use it to parse the field.</p><pre>// The value of the Signature-Input HTTP field<br>String signatureInputFieldValue =<br>        &quot;sig-b24=(\&quot;@status\&quot; \&quot;content-type\&quot; &quot; +<br>        &quot;\&quot;content-digest\&quot; \&quot;content-length\&quot;);created=1618884473&quot; +<br>        &quot;;keyid=\&quot;test-key-ecc-p256\&quot;&quot;;<br><br>// Parse the value of the Signature-Input HTTP field<br>SignatureInputField signatureInputField =<br>        SignatureInputField.parse(signatureInputFieldValue);</pre><h4>Extract Signature-Metadata Pairs</h4><p>Signatures and metadata are placed separately in the Signature HTTP field and the Signature-Input HTTP field, respectively. A signature and its corresponding metadata are associated by sharing the same label. In the example in RFC 9421, Section B.2.4, the label sig-b24 is used.</p><p>The process of examining both the Signature HTTP field and the Signature-Input HTTP field and extracting pairs of signatures and metadata is handled by the scan method of the SignatureEntry class.</p><pre>Map&lt;String, SignatureEntry&gt; signatureEntries =<br>        SignatureEntry.scan(signatureField, signatureInputField);</pre><p>The keys of the Map instance returned by the scan method are the labels. By specifying a label, a particular SignatureEntry instance can be retrieved.</p><pre>SignatureEntry signatureEntry = signatureEntries.get(&quot;sig-b24&quot;);</pre><p>The SignatureEntry class has properties that represent the label (String), the signature (byte[]), and the metadata (SignatureMetadata). They can be accessed via the getLabel(), getSignature(), and getMetadata() methods, respectively.</p><h4>Prepare Component Values</h4><p>In the test suite in RFC 9421, <a href="https://www.rfc-editor.org/rfc/rfc9421.html#appendix-B.2">Section B.2 “Test Cases”</a>, the following HTTP message is commonly used for tests that operate on HTTP responses.</p><pre>HTTP/1.1 200 OK<br>Date: Tue, 20 Apr 2021 02:07:56 GMT<br>Content-Type: application/json<br>Content-Digest: sha-512=:mEWXIS7MaLRuGgxOBdODa3xqM1XdEvxoYhvlCFJ41Q\<br>  JgJc4GTsPp29l5oGX69wWdXymyU0rjJuahq4l5aGgfLQ==:<br>Content-Length: 23<br><br>{&quot;message&quot;: &quot;good dog&quot;}</pre><p>Before constructing the signature base, it is necessary to prepare a SignatureContext to access the set of HTTP message components contained in this HTTP message (RFC 9421, <a href="https://www.rfc-editor.org/rfc/rfc9421.html#section-2">Section 2 “HTTP Message Components”</a>).</p><p>By using the ComponentValueProvider class, an implementation of the SignatureContext interface can be prepared relatively easily.</p><pre>class ResponseSignatureContext extends ComponentValueProvider<br>{<br>    ResponseSignatureContext()<br>    {<br>        setStatus(200);<br>        setHeaders(buildHeaders());<br>    }<br><br>    private static Map&lt;String, List&lt;String&gt;&gt; buildHeaders()<br>    {<br>        Map&lt;String, List&lt;String&gt;&gt; headers = new LinkedHashMap&lt;&gt;();<br><br>        headers.put(&quot;Date&quot;,           List.of(&quot;Tue, 20 Apr 2021 02:07:56 GMT&quot;));<br>        headers.put(&quot;Content-Type&quot;,   List.of(&quot;application/json&quot;));<br>        headers.put(&quot;Content-Digest&quot;, List.of(&quot;sha-512=:mEWXIS7MaLRuGgxOBdODa3xqM1XdEvxoYhvlCFJ41QJgJc4GTsPp29l5oGX69wWdXymyU0rjJuahq4l5aGgfLQ==:&quot;));<br>        headers.put(&quot;Content-Length&quot;, List.of(&quot;23&quot;));<br><br>        return headers;<br>    }<br>}</pre><h4>Construct the Signature Base</h4><p>By providing a SignatureContext and SignatureMetadata to the utility class SignatureBaseBuilder, you can generate a SignatureBase instance that represents the signature base.</p><pre>SignatureBase signatureBase =<br>        new SignatureBaseBuilder(new ResponseSignatureContext())<br>        .build(signatureEntry.getMetadata());</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LeuQ1974OEKQP8k_NmNEoQ.png" /></figure><h4>Prepare the Verification Key</h4><p>The HTTP message signature in B.2.4 is generated using the key from <a href="https://www.rfc-editor.org/rfc/rfc9421.html#appendix-B.1.3">B.1.3, “Example ECC P-256 Test Key.”</a> Based on this key, we prepare the key to be used for signature verification.</p><pre>// The key from RFC 9421, B.1.3. Example ECC P-256 Test Key<br>// (NOTE: &quot;alg&quot; is added)<br>String TEST_KEY_ECC_P256 =<br>        &quot;{\n&quot; +<br>        &quot;  \&quot;kty\&quot;: \&quot;EC\&quot;,\n&quot; +<br>        &quot;  \&quot;alg\&quot;: \&quot;ES256\&quot;,\n&quot; +<br>        &quot;  \&quot;crv\&quot;: \&quot;P-256\&quot;,\n&quot; +<br>        &quot;  \&quot;kid\&quot;: \&quot;test-key-ecc-p256\&quot;,\n&quot; +<br>        &quot;  \&quot;d\&quot;: \&quot;UpuF81l-kOxbjf7T4mNSv0r5tN67Gim7rnf6EFpcYDs\&quot;,\n&quot; +<br>        &quot;  \&quot;x\&quot;: \&quot;qIVYZVLCrPZHGHjP17CTW0_-D9Lfw0EkjqF7xB4FivA\&quot;,\n&quot; +<br>        &quot;  \&quot;y\&quot;: \&quot;Mc4nN9LTDOBhfoUeg8Ye9WedFRhnZXZJA12Qp0zZ6F0\&quot;\n&quot; +<br>        &quot;}&quot;;<br><br>// Prepare the verification key<br>// (NOTE: JWK is a class from the Nimbus JOSE + JWT library)<br>JWK verificationKey = JWK.parse(TEST_KEY_ECC_P256).toPublicJWK();</pre><h4>Prepare the Verifier</h4><p>A signature verifier is represented by the HttpVerifier interface. If a JWK instance is available, you can create an instance of JoseHttpVerifier, which is an implementation of this interface.</p><pre>HttpVerifier verifier = new JoseHttpVerifier(verificationKey);</pre><h4>Verify the Signature</h4><p>The signature can be verified by passing a signature verifier (HttpVerifier) and the signature (byte[]) to the verify method of the SignatureBase class. If the signature passes verification, the return value is true.</p><pre>boolean verified = signatureBase.verify(verifier, signatureEntry.getSignature());</pre><h3>Conclusion</h3><p>HTTP message signatures can be used as one mechanism for protecting APIs. They are introduced in the online document <em>“</em><a href="https://www.authlete.com/developers/api_protection/"><strong><em>Comprehensive API Protection with Standard Specifications</em></strong></a><em>”</em>, so please take a look.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=501bbdc7dfec" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[CIMD Metadata Policy]]></title>
            <link>https://darutk.medium.com/cimd-metadata-policy-c25969b538b6?source=rss-f485feca545e------2</link>
            <guid isPermaLink="false">https://medium.com/p/c25969b538b6</guid>
            <category><![CDATA[oauth]]></category>
            <category><![CDATA[cimd]]></category>
            <category><![CDATA[mcps]]></category>
            <dc:creator><![CDATA[Takahiko Kawasaki]]></dc:creator>
            <pubDate>Sun, 14 Dec 2025 19:22:48 GMT</pubDate>
            <atom:updated>2025-12-14T19:22:48.248Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>In the November 25, 2025 version of the MCP specification, <a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/"><strong>CIMD</strong></a> was adopted as one of the standard specifications that make up MCP (<a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization">MCP 2025–11–25 Authorization</a>).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tN8MpvqsoCgzTEe2435e9w.png" /></figure><p>While an explanation of CIMD itself is covered in the Authlete website document <em>“</em><a href="https://www.authlete.com/developers/cimd/"><em>OAuth Client ID Metadata Document (CIMD)</em></a><em>”</em>, this article focuses on how to adjust client metadata obtained through the CIMD mechanism.</p><h3>No Way to Verify a Client’s Trustworthiness</h3><p>People are riding the wave of excitement, saying, <em>“CIMD replaces DCR!”</em></p><p>📝 <em>NOTE: DCR stands for Dynamic Client Registration and, depending on the context, refers to </em><a href="https://openid.net/specs/openid-connect-registration-1_0.html"><em>OpenID Connect Dynamic Client Registration 1.0</em></a><em>, </em><a href="https://www.rfc-editor.org/rfc/rfc7591.html"><em>RFC 7591 OAuth 2.0 Dynamic Client Registration Protocol</em></a><em>, or both.</em></p><p>However, let’s calm down. The idea of using a URL as the “client ID” in OAuth 2.0 or OpenID Connect and retrieving client metadata from the location reachable via that URL had already been realized, even before CIMD, in the <a href="https://openid.net/specs/openid-federation-1_0.html"><strong>OpenID Federation 1.0</strong></a> specification (see the <a href="https://www.authlete.com/developers/oidcfed/">explanatory article</a>).</p><p>The functionality of <em>“fetching client metadata from a remote server during request processing, dynamically registering the client based on it, and then continuing to process the request as if nothing happened”</em> has a significant impact on the implementation of an authorization server. After all, it affects every place where a client ID can appear.</p><p>📝 <em>NOTE: The client ID may appear in obvious places, such as the </em><em>client_id request parameter in authorization requests or token requests. However, it can also appear in less obvious places hidden in the details of individual specifications — for example, in the </em><em>iss claim of a client assertion (</em><a href="https://www.rfc-editor.org/rfc/rfc7523.html"><em>RFC 7523</em></a><em>) or a request object (</em><a href="https://www.rfc-editor.org/rfc/rfc9101.html"><em>RFC 9101</em></a><em>), or in the </em><em>aud claim of an ID token (</em><a href="https://openid.net/specs/openid-connect-core-1_0.html"><em>OIDC Core</em></a><em>) or a JWT Authorization Response (</em><a href="https://openid.net/specs/oauth-v2-jarm.html"><em>JARM</em></a><em>).</em></p><p>Moreover, dynamically retrieved client metadata has an expiration time, so the authorization server must also implement logic to re-fetch the latest metadata.</p><p>For these reasons, you may find the CIMD specification overwhelming at first.</p><p>However, for those who are familiar with OpenID Federation 1.0, the reaction is quite the opposite: <strong>CIMD looks too simple — or even too lax from a security perspective.</strong></p><p>The CIMD specification does not define any way to verify whether a client is a trustworthy communication partner. As a result, <strong>unless an authorization server implementation imposes some form of custom restrictions, anyone can freely register a client with an authorization server that supports CIMD</strong>. This stands in contrast to OpenID Federation 1.0, which explicitly defines how to establish trust relationships between entities using <strong>trust chains</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CoZ6CIRihjBfYGlJSg6UHA.png" /><figcaption>Trust Chain of OpenID Federation 1.0</figcaption></figure><p>📝 <em>NOTE: Authlete’s CIMD implementation includes a feature for configuring an allowlist, which lets you restrict the URLs that are considered valid as client IDs. By using this feature, you can avoid the problem of “anyone being able to register a client arbitrarily.”</em></p><h3>No Way to Adjust Client Metadata</h3><p>The CIMD specification was promoted from an individual draft to an IETF working group draft just two months ago, in October 2025, so at this point there are only a limited number of real, working implementations. Against that backdrop, Visual Studio Code publishes its CIMD client metadata at<br><a href="https://vscode.dev/oauth/client-metadata.json">https://vscode.dev/oauth/client-metadata.json</a>.</p><pre>{<br>  &quot;client_name&quot;: &quot;Visual Studio Code&quot;,<br>  &quot;grant_types&quot;: [<br>    &quot;authorization_code&quot;,<br>    &quot;refresh_token&quot;,<br>    &quot;urn:ietf:params:oauth:grant-type:device_code&quot;<br>  ],<br>  &quot;response_types&quot;: [<br>    &quot;code&quot;<br>  ],<br>  &quot;token_endpoint_auth_method&quot;: &quot;none&quot;,<br>  &quot;application_type&quot;: &quot;native&quot;,<br>  &quot;client_id&quot;: &quot;https://vscode.dev/oauth/client-metadata.json&quot;,<br>  &quot;client_uri&quot;: &quot;https://vscode.dev/product&quot;,<br>  &quot;redirect_uris&quot;: [<br>    &quot;http://127.0.0.1:33418/&quot;,<br>    &quot;https://vscode.dev/redirect&quot;<br>  ]<br>}</pre><p>The above is Visual Studio Code’s client metadata as of December 15, 2025. If this metadata is registered as-is, a CIMD implementation that follows the OpenID Connect Dynamic Client Registration 1.0 specification (DCR) would set the value of the client’s id_token_signed_response_alg property to RS256. This is because the DCR specification defines RS256 as the default value when id_token_signed_response_alg is omitted.</p><p>As a result, the ID tokens issued to that client will be signed using RS256. However, some servers do not allow this algorithm, which leads to a situation where the client can be registered successfully, but ID tokens cannot be issued.</p><p>📝 <em>NOTE: The </em><a href="https://openid.net/specs/fapi-security-profile-2_0-final.html"><em>FAPI 2.0 Security Profile</em></a><em> disallows the use of any algorithms other than </em><em>PS256, </em><em>ES256, and </em><em>EdDSA using </em><em>Ed25519.</em></p><p>However, since the CIMD specification does not even define a way to verify whether a client is a trustworthy communication partner, it naturally does not define any mechanism for adjusting metadata either.</p><h3>Metadata Policy</h3><p>On the other hand, OpenID Federation 1.0 defines a mechanism that allows metadata claimed by a leaf entity as its own to be modified based on the policies of <strong>trust anchors</strong> and <strong>intermediate authorities</strong> that make up the trust chain. Such policies are called <strong><em>Metadata Policies</em></strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2RVLUpwMXVBa3vR996p3aw.png" /><figcaption>Metadata Policies of OpenID Federation 1.0</figcaption></figure><p>For example, suppose we have the following client metadata:</p><pre>{<br>  &quot;client_name&quot;: &quot;Example Client&quot;,<br>  &quot;grant_types&quot;: [<br>    &quot;authorization_code&quot;,<br>    &quot;refresh_token&quot;<br>  ],<br>  &quot;response_types&quot;: [<br>    &quot;code&quot;<br>  ],<br>  &quot;token_endpoint_auth_method&quot;: &quot;none&quot;,<br>  &quot;client_id&quot;: &quot;https://example.com/client.json&quot;,<br>  &quot;redirect_uris&quot;: [<br>    &quot;https://example.com/redirect&quot;<br>  ]<br>}</pre><p>When the following metadata policy is applied to this client metadata,</p><pre>{<br>  &quot;id_token_signed_response_alg&quot;: {<br>    &quot;default&quot;: &quot;ES256&quot;,<br>    &quot;one_of&quot;: [&quot;ES256&quot;, &quot;ES384&quot;, &quot;ES512&quot;]<br>  },<br>  &quot;redirect_uris&quot;: {<br>    &quot;add&quot;: [<br>      &quot;http://localhost:12345/redirect&quot;<br>    ]<br>  }<br>}</pre><p>the following client metadata is obtained. Note that http://localhost:12345/redirect has been added to redirect_uris, and that a new id_token_signed_response_alg property with the value ES256 has been introduced.</p><pre>{<br>  &quot;client_name&quot;: &quot;Example Client&quot;,<br>  &quot;grant_types&quot;: [<br>    &quot;authorization_code&quot;,<br>    &quot;refresh_token&quot;<br>  ],<br>  &quot;response_types&quot;: [<br>    &quot;code&quot;<br>  ],<br>  &quot;token_endpoint_auth_method&quot;: &quot;none&quot;,<br>  &quot;client_id&quot;: &quot;https://example.com/client.json&quot;,<br>  &quot;redirect_uris&quot;: [<br>    &quot;https://example.com/redirect&quot;,<br>    &quot;http://localhost:12345/redirect&quot;<br>  ],<br>  &quot;id_token_signed_response_alg&quot;: &quot;ES256&quot;<br>}</pre><p>📝 <em>NOTE: The syntax of metadata policies is defined in </em><a href="https://openid.net/specs/openid-federation-1_0.html#name-metadata-policy"><em>Section 6.1, “Metadata Policy,”</em></a><em> of the OpenID Federation 1.0 specification.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H6LJ3xTgzy8dnEsiNAGWJA.png" /></figure><h3>CIMD Metadata Policy</h3><p>OpenID Federation 1.0 defines a mechanism for adjusting metadata. However, the later CIMD specification does not include such a mechanism. Therefore, if equivalent functionality is desired, authorization servers or OpenID providers must implement it as a proprietary extension.</p><p>There are several possible ways to implement this, but since OpenID Federation 1.0 already defines a flexible metadata policy syntax (<a href="https://openid.net/specs/openid-federation-1_0.html#name-metadata-policy">Section 6.1</a>), it makes sense to leverage it.</p><p>And just as trust anchors and intermediate authorities in OpenID Federation 1.0 have metadata policies, the most straightforward approach is for the authorization server itself to have a metadata policy.</p><p>For this purpose, <a href="https://www.authlete.com/"><strong>Authlete</strong></a> has added the following set of properties to the Service.</p><ul><li>cimdMetadataPolicyEnabled<strong> </strong>— A boolean value indicating whether a metadata policy should be applied to client metadata obtained via CIMD.</li><li>cimdMetadataPolicy<strong> — </strong>The metadata policy to be applied to client metadata obtained via CIMD.</li></ul><p>For example, by configuring these properties as shown below,</p><pre>{<br>  &quot;cimdMetadataPolicyEnabled&quot;: true,<br>  &quot;cimdMetadataPolicy&quot;: &quot;{\&quot;id_token_signed_response_alg\&quot;:{\&quot;default\&quot;:\&quot;ES256\&quot;}}&quot;<br>}</pre><p>you can set the default value of the id_token_signed_response_alg property to ES256.</p><h3>Conclusion</h3><p>Because the MCP specification has decided to recommend CIMD as a replacement for DCR, we will likely see a series of blog posts comparing CIMD and DCR for some time. However, I believe that <strong>CIMD should be compared not with DCR, but with OpenID Federation 1.0</strong>.</p><p>Such a comparison makes it clear what CIMD is missing and what kinds of additional features need to be implemented. The “<strong>CIMD Metadata Policy</strong>” feature introduced in this article is a capability that could only be designed and implemented by understanding the metadata policy defined in OpenID Federation 1.0 (<a href="https://openid.net/specs/openid-federation-1_0.html#name-metadata-policy">Section 6.1</a>).</p><h3>Event Announcement</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/1*Dd9gOFHa12xUvkKUCnZGNA.png" /></figure><p>At the event <strong>“</strong><a href="https://openid.connpass.com/event/376275/"><strong>OpenID BizDay #18 ~ AIdentity × Security CollabDay</strong></a><strong>”</strong>, scheduled to be held in Tokyo on December 19, 2025, a member of our team will give a lightning talk titled <strong>“Implementing an MCP OAuth 2.0 Authorization Server with Authlete,”</strong> which will include a <strong>CIMD demo</strong>. Stay tuned!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c25969b538b6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Managing Liquibase Changelogs with a Hybrid Structure]]></title>
            <link>https://darutk.medium.com/liquibase-hybrid-structure-760e393ff671?source=rss-f485feca545e------2</link>
            <guid isPermaLink="false">https://medium.com/p/760e393ff671</guid>
            <category><![CDATA[liquibase]]></category>
            <category><![CDATA[database]]></category>
            <dc:creator><![CDATA[Takahiko Kawasaki]]></dc:creator>
            <pubDate>Thu, 04 Dec 2025 09:56:41 GMT</pubDate>
            <atom:updated>2025-12-04T09:56:41.896Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p><a href="https://www.liquibase.com/"><strong>Liquibase</strong></a> is a tool for managing the revision history of database schema definitions.</p><p>Liquibase manages schema changes using <strong>changelogs</strong>, which are composed of multiple <strong>changesets</strong>. Each changeset describes a specific modification to be applied to the database.</p><p>In the official Liquibase documentation, two approaches are presented for structuring changelogs: one that is object-based and another that is release-based. In this article, I will refer to them as the <strong>object-oriented structure</strong> and the <strong>release-oriented structure</strong>, respectively.</p><p>Both approaches have their own strengths and weaknesses. This article introduces a <strong>hybrid structure</strong> that combines the advantages of both while mitigating their drawbacks.</p><h3>Object-Oriented Structure</h3><p>In the object-oriented structure, the changelog hierarchy is organized from the perspective of database objects such as tables, views, and indexes.</p><pre>changelog-root.yaml<br>|<br>|-- changelog-table.yaml<br>|   |<br>|   |-- changelog-table-user.yaml<br>|   |<br>|   |-- ...<br>|<br>|-- changelog-view.yaml<br>|</pre><p>The advantage of this structure is that it makes it easy to find the change history for a specific object. The downside, however, is that you can’t immediately tell which release a given change belongs to.</p><h3>Release-Oriented Structure</h3><p>In the release-oriented structure, the changelog hierarchy is organized from the perspective of releases.</p><pre>changelog-root.yaml<br>|<br>|-- changelog-1.0.yaml<br>|   |<br>|   |-- changelog-1.0.0.yaml<br>|   |<br>|   |-- ...<br>|<br>|-- changelog-1.1.yaml<br>|</pre><p>The advantage of this structure is that it makes it easy to see which changes are included in a particular release. Conversely, the drawback is that it becomes harder to track the change history for each individual object.</p><h3>Hybrid Structure</h3><p>Both structures have their own advantages and disadvantages. However, by combining these approaches, you can gain the benefits of both while eliminating their respective drawbacks. I will refer to this method as the <strong>hybrid structure</strong>.</p><p>This structure involves the following set of files:</p><ul><li><strong>changelog.yaml</strong> — The root file</li><li><strong>changelog-version-X.Y[.Z].yaml</strong> — Files whose names include a version number</li><li><strong>changelog-date-YY-MM-DD.yaml</strong> — Files whose names include a date</li><li><strong>OBJECT-NAME-NNNN.yaml</strong> — Files whose names include the object type, object name, and a sequence number (changesets)</li></ul><p>The relationships among these files are as follows:</p><ul><li>The root file, <strong>changelog.yaml</strong>, <a href="https://docs.liquibase.com/reference-guide/changelog-attributes/include">includes</a> all <strong>changelog-version-X.Y.yaml</strong> files.</li><li>Each <strong>changelog-version-X.Y.yaml</strong> file includes one or more <strong>changelog-version-X.Y.Z.yaml</strong> files.</li><li>Each <strong>changelog-version-X.Y.Z.yaml</strong> file includes one or more <strong>changelog-date-YY-MM-DD.yaml</strong> files.</li><li>Each <strong>changelog-date-YY-MM-DD.yaml</strong> file includes one or more <strong>OBJECT-NAME-NNNN.yaml</strong> files.</li></ul><p>These files are placed in the following directory structure.</p><pre>changelog.yaml<br>changelogs/version/X.Y/changelog-version-X.Y.yaml<br>changelogs/version/X.Y/changelog-version-X.Y.Z.yaml<br>changelogs/date/YYYY/MM/changelog-date-YYYY-MM-DD.yaml<br>chnagesets/OBJECT/NAME/OBJECT-NAME-NNNN.yaml</pre><h3>Hybrid Structure Example</h3><p>Below is an example of a directory structure and file names when using the hybrid structure.</p><pre>.<br>├── changelog.yaml<br>├── changelogs<br>│   ├── date<br>│   │   └── 2025<br>│   │       ├── 04<br>│   │       │   └── changelog-date-2025-04-09.yaml<br>│   │       ├── 06<br>│   │       │   └── changelog-date-2025-06-10.yaml<br>│   │       ├── 07<br>│   │       │   ├── changelog-date-2025-07-11.yaml<br>│   │       │   ├── changelog-date-2025-07-16.yaml<br>│   │       │   ├── changelog-date-2025-07-26.yaml<br>│   │       │   └── changelog-date-2025-07-28.yaml<br>│   │       ├── 08<br>│   │       │   └── changelog-date-2025-08-28.yaml<br>│   │       └── 09<br>│   │           └── changelog-date-2025-09-06.yaml<br>│   └── version<br>│       └── 1.0<br>│           ├── changelog-version-1.0.0.yaml<br>│           ├── changelog-version-1.0.1.yaml<br>│           ├── changelog-version-1.0.2.yaml<br>│           ├── changelog-version-1.0.3.yaml<br>│           ├── changelog-version-1.0.4.yaml<br>│           ├── changelog-version-1.0.5.yaml<br>│           ├── changelog-version-1.0.6.yaml<br>│           ├── changelog-version-1.0.7.yaml<br>│           └── changelog-version-1.0.yaml<br>└── changesets<br>    └── table<br>        ├── arena_key<br>        │   └── table-arena_key-0000.yaml<br>        ├── ssf_stream<br>        │   ├── table-ssf_stream-0000.yaml<br>        │   └── table-ssf_stream-0001.yaml<br>        ├── ssf_stream_event<br>        │   └── table-ssf_stream_event-0000.yaml<br>        ├── ssf_stream_subject<br>        │   └── table-ssf_stream_subject-0000.yaml<br>        ├── ssf_transmitter<br>        │   ├── table-ssf_transmitter-0000.yaml<br>        │   └── table-ssf_transmitter-0001.yaml<br>        └── ssf_transmitter_key<br>            └── table-ssf_transmitter_key-0000.yaml</pre><pre># changelog.yaml<br>databaseChangeLog:<br>  - logicalFilePath: changelog.yaml<br>  - include:<br>      file: changelogs/version/1.0/changelog-version-1.0.yaml</pre><pre># changelog-version-1.0.yaml<br>databaseChangeLog:<br>  - include:<br>      file: changelogs/version/1.0/changelog-version-1.0.0.yaml<br>  - include:<br>      file: changelogs/version/1.0/changelog-version-1.0.1.yaml<br>  - include:<br>      file: changelogs/version/1.0/changelog-version-1.0.2.yaml<br>  - include:<br>      file: changelogs/version/1.0/changelog-version-1.0.3.yaml<br>  - include:<br>      file: changelogs/version/1.0/changelog-version-1.0.4.yaml<br>  - include:<br>      file: changelogs/version/1.0/changelog-version-1.0.5.yaml<br>  - include:<br>      file: changelogs/version/1.0/changelog-version-1.0.6.yaml<br>  - include:<br>      file: changelogs/version/1.0/changelog-version-1.0.7.yaml</pre><pre># changelog-version-1.0.0.yaml<br>databaseChangeLog:<br>  - include:<br>      file: changelogs/date/2025/04/changelog-date-2025-04-09.yaml</pre><pre># changelog-date-2025-04-09.yaml<br>databaseChangeLog:<br>  - include:<br>      file: changesets/table/ssf_transmitter/table-ssf_transmitter-0000.yaml</pre><pre># table-ssf_transmitter-0000.yaml<br>databaseChangeLog:<br>  - changeSet:<br>      id: table-ssf_transmitter-0000<br>      author: authlete<br>      changes:<br>        - createTable:<br>            tableName: ssf_transmitter<br>            columns:<br>      # Omitted below</pre><h3>Considerations for Changesets</h3><p>Each changeset has an id attribute and an author attribute. Liquibase identifies each changeset by combining these attribute values with the path to the root changelog file used when the liquibase command is executed. Based on whether this combination exists in the change tracking table (DATABASECHANGELOG), Liquibase determines whether the changeset should be applied.</p><p>However, in my project, each changeset is uniquely identified by its id attribute alone. The author attribute and the file path are intentionally kept identical across all changesets.</p><p>In the past, we used a convention where the author attribute contained an identifier for the developer who created the changeset. But in my experience, this approach has never provided any practical benefit. On the contrary, it felt increasingly odd that arbitrary developer-specific strings would remain permanently in the change tracking table and could never be modified later. For that reason, in new projects, we set the author attribute to a fixed string, <a href="https://www.authlete.com/">authlete</a> (our company name).</p><p>Another issue is that the file path becomes part of a changeset’s identifier. Even if the contents of a changeset remain unchanged, Liquibase will treat it as a different changeset if the file path changes. Moreover, even when the file path itself hasn’t changed, Liquibase may still treat it as a different changeset if it resolves the changelog or changeset location differently — for example, if the value of the LIQUIBASE_CLASSPATH environment variable changes. When using Liquibase via a Docker image, mounting a volume that contains the changelogs can also result in different paths. Frankly speaking, Liquibase’s design of using file paths as part of changeset identification is problematic.</p><p>You can avoid file-path-related issues by fixing the logical path of the changelog file using <a href="https://docs.liquibase.com/reference-guide/changelog-attributes/logicalfilepath">logicalFilePath</a> in changelog.yaml. Below is the beginning of the changelog.yaml file. Note the logicalFilePath attribute.</p><pre># changelog.yaml<br>databaseChangeLog:<br>  - logicalFilePath: changelog.yaml<br>  - include:<br>      file: changelogs/version/1.0/changelog-version-1.0.yaml</pre><h3>Conclusion</h3><p>The hybrid structure is something I devised myself, so it doesn’t have a track record in the wider community yet. However, if you’re struggling with how to structure your Liquibase changelogs, I encourage you to give it a try.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=760e393ff671" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SD-JWT Programming]]></title>
            <link>https://darutk.medium.com/sd-jwt-programming-df1e43560aa9?source=rss-f485feca545e------2</link>
            <guid isPermaLink="false">https://medium.com/p/df1e43560aa9</guid>
            <category><![CDATA[sd-jwt]]></category>
            <category><![CDATA[verifiable-credentials]]></category>
            <category><![CDATA[jwt]]></category>
            <dc:creator><![CDATA[Takahiko Kawasaki]]></dc:creator>
            <pubDate>Thu, 27 Nov 2025 09:37:33 GMT</pubDate>
            <atom:updated>2025-11-27T09:37:33.149Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p><strong>Selective Disclosure</strong> using JWTs — known as <strong>SD-JWT</strong> — has finally been published by the <a href="https://www.ietf.org/">IETF</a> as an official standard: <a href="https://www.rfc-editor.org/rfc/rfc9901.html"><strong>RFC 9901 Selective Disclosure for JSON Web Tokens</strong></a>. Following <a href="https://www.rfc-editor.org/rfc/rfc9449.html">RFC 9449</a> (DPoP), I’m grateful that my company, <a href="https://www.authlete.com/">Authlete</a>, is once again listed among the specification authors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/837/1*G9wF0QoT0ApeDoDuDvQrRg.png" /></figure><p>Because SD-JWT also serves as the foundation for <a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/"><strong>SD-JWT VC</strong></a>, one of the formats for Verifiable Credentials, it is also gaining attention in the context of digital identity.</p><p>There are already numerous articles that cover the purpose and format of SD-JWT, so I won’t repeat those explanations here. Instead, this article assumes you are already familiar with the SD-JWT specification and focuses on how to actually read and write SD-JWTs in code.</p><p>Programming will be done using Authlete’s open-source Java SD-JWT library (<strong>authlete/</strong><a href="https://github.com/authlete/sd-jwt"><strong>sd-jwt</strong></a>). This library is used not only in Authlete’s implementation of the <a href="https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html">OpenID for Verifiable Credential Issuance 1.0</a> specification, but also in the official <a href="https://openid.net/certification/about-conformance-suite/">conformance suite</a> provided by the <a href="https://openid.net/">OpenID Foundation</a>.</p><ul><li>Source Code: <a href="https://github.com/authlete/sd-jwt">https://github.com/authlete/sd-jwt</a></li><li>JavaDoc: <a href="https://authlete.github.io/sd-jwt">https://authlete.github.io/sd-jwt</a></li></ul><h3>Library Setup</h3><p>The SD-JWT library is published in the <a href="https://central.sonatype.com/">Maven Central Repository</a> under the name <a href="https://central.sonatype.com/artifact/com.authlete/sd-jwt">com.authlete:sd-jwt</a>. You can use it by adding the following dependency to your pom.xml file:</p><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;com.authlete&lt;/groupId&gt;<br>    &lt;artifactId&gt;sd-jwt&lt;/artifactId&gt;<br>    &lt;version&gt;${sd-jwt.version}&lt;/version&gt;<br>&lt;/dependency&gt;</pre><p>Replace ${sd-jwt.version} with the actual version number you intend to use.</p><p><em>NOTE: The SD-JWT library depends on </em><a href="https://github.com/google/gson"><em>GSON</em></a><em>.</em></p><h3>Disclosure</h3><p>The following diagram illustrates the structure of an SD-JWT.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qc49ue8qpsW1MDUvlftigw.png" /></figure><p><strong>Disclosures</strong> are an essential component of SD-JWT. In this SD-JWT library, each Disclosure is represented by the Disclosure class.</p><p>A Disclosure consists of three elements — salt, claim name, and claim value — when it corresponds to a property of an object. In contrast, when it corresponds to an array element, it consists of only two elements: salt and claim value. The difference is simply whether a claim name is included.</p><p>To create a Disclosure representing an object property, use one of the following constructors, both of which accept the claim name as an argument:</p><pre>Disclosure(String claimName, Object claimValue)<br>Disclosure(String salt, String claimName, Object claimValue)</pre><p>When you use the constructor with two arguments, the salt is automatically generated inside the constructor.</p><pre>// Three arguments passed to a Disclosure constructor<br>String salt       = &quot;_26bc4LT-ac6q2KI6cBW5es&quot;;<br>String claimName  = &quot;family_name&quot;;<br>Object claimValue = &quot;Möbius&quot;;<br><br>// Create a Disclosure instance<br>Disclosure disclosure = new Disclosure(salt, claimName, claimValue);</pre><pre>// Two arguments passed to a Disclosure constructor<br>String claimName  = &quot;family_name&quot;;<br>Object claimValue = &quot;Möbius&quot;;<br><br>// Create a Disclosure instance with a salt generated automatically<br>Disclosure disclosure = new Disclosure(claimName, claimValue);</pre><p>To create a Disclosure representing an array element, either pass null for the claim name argument or simply use the constructor that accepts only a single argument.</p><pre>// A claim value (an array element)<br>Object claimValue = &quot;array-element0&quot;;<br><br>// Create a Disclosure instance<br>Disclosure disclosure = new Disclosure(claimValue);</pre><p>Once you have created a Disclosure instance, you can obtain its serialized form using either the getDisclosure() method or the toString() method.</p><pre>// Serialize the Disclosure<br>// serialized1 and serialized2 will have the same value<br>String serialized1 = disclosure.getDisclosure();<br>String serialized2 = disclosure.toString();</pre><p>You can create a Disclosure instance from its serialized form using the parse(String) method.</p><pre>// Parse a serialized Disclosure<br>Disclosure disclosure = Disclosure.parse(<br>    &quot;WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0&quot;);</pre><h3>Disclosure Digest</h3><p>In the payload of the SD-JWT Issuer-signed JWT, it is not the Disclosure itself but the digest of the Disclosure that is included. You can obtain the digest using one of the following methods:</p><pre>digest()<br>digest(String hashAlgorithm)</pre><p>The digest() method without arguments calculates the digest using the default hash algorithm, SHA-256. On the other hand, the digest(String) method takes a String argument specifying the hash algorithm to use for computing the digest.</p><pre>// Obtain the digest of a Disclosure using the default hash algorithm (SHA-256)<br>String digestSha256 = disclosure.digest();<br><br>// Obtain the digest using the SHA-512 hash algorithm<br>String digestSha512 = disclosure.digest(&quot;sha-512&quot;);</pre><h3>Selective Disclosure Object</h3><p>The SDObjectBuilder class is a utility for creating a Map instance that represents a JSON object containing the _sd array.</p><p>A typical usage pattern for SDObjectBuilder is as follows:</p><ol><li>Create an instance of SDObjectBuilder.</li><li>Add regular claims as needed.</li><li>Add digest values of Disclosures as needed.</li><li>Call the build() method to generate the Map instance.</li></ol><pre>// Create an SDObjectBuilder instance<br>SDObjectBuilder builder = new SDObjectBuilder();<br><br>// Add a regular claim<br>builder.putClaim(&quot;my_claim_name&quot;, &quot;my_claim_value&quot;);<br><br>// Add the digest of a Disclosure<br>Disclosure disclosure = new Disclosure(<br>    &quot;_26bc4LT-ac6q2KI6cBW5es&quot;, &quot;family_name&quot;, &quot;Möbius&quot;);<br>builder.putSDClaim(disclosure);<br><br>// Generate the Map instance<br>Map&lt;String, Object&gt; map = builder.build();</pre><p>The Map instance obtained from the code above contains the same content as the following JSON:</p><pre>{<br>    &quot;my_claim_name&quot;: &quot;my_claim_value&quot;,<br>    &quot;_sd&quot;: [<br>        &quot;TZjouOTrBKEwUNjNDs9yeMzBoQn8FFLPaJjRRmAtwrM&quot;<br>    ]<br>}</pre><p>If you want to specify a hash algorithm other than the default (SHA-256) for computing Disclosure digests in SDObjectBuilder, use the constructor that accepts the algorithm name as an argument.</p><pre>// SDObjectBuilder constructor that takes a hash algorithm as an argument<br>SDObjectBuilder builder = new SDObjectBuilder(&quot;sha-512&quot;);</pre><p>When generating the Map instance, if you call build(true) instead of build(), the name of the hash algorithm will be included in the Map as the value of the _sd_alg claim.</p><pre>// Embed the hash algorithm in the Map<br>Map&lt;String, Object&gt; map = builder.build(true);</pre><p>The following JSON illustrates the data contained in a Map instance generated using build(true):</p><pre>{<br>    &quot;_sd&quot;: [<br>        &quot;j35wlGQlyQ8b4OE3Py6l3AAvOskjcNOxj0SsiVSrVdmVs8bapSUelViRDbmlntFABkp6_zSz1fA-dlWGUxGpEA&quot;<br>    ],<br>    &quot;_sd_alg&quot;: &quot;sha-512&quot;<br>}</pre><h3>Decoy Digest</h3><p>To prevent an attacker from easily guessing the original number of claims, you can include meaningless Disclosure digest values that do not correspond to any actual claims. These digest values are called <strong>Decoy Digests</strong> (<a href="https://www.rfc-editor.org/rfc/rfc9901.html#section-4.2.5">RFC 9901 Section 4.2.5, Decoy Digests</a>).</p><p>You can add Decoy Digests using the putDecoyDigest() or putDecoyDigests(int) methods.</p><pre>// Add a single Decoy Digest<br>builder.putDecoyDigest();<br><br>// Add the specified number of Decoy Digests<br>builder.putDecoyDigests(3);</pre><h3>Issuer-signed JWT</h3><p>When an SD-JWT string is split by ~, the first element is a JWT. In RFC 9901, this JWT is called the <strong>Issuer-signed JWT</strong>. Note that in the authlete/sd-jwt library, which was implemented when the specification was still in flux, this JWT is referred to as the <strong>Credential JWT</strong>.</p><p>The authlete/sd-jwt library provides the SDObjectBuilder class as a utility for generating the payload of this JWT. However, the library does not provide utilities for creating the JWT itself.</p><p>This is an intentional design choice, allowing developers to use any JWT library they prefer. Personally, I recommend the <a href="https://connect2id.com/products/nimbus-jose-jwt">Nimbus JOSE + JWT</a> library, but the authlete/sd-jwt library itself does not depend on Nimbus JOSE + JWT.</p><p>Below is an example program that creates an Issuer-signed JWT. The SDObjectBuilder class from authlete/sd-jwt is used to generate the payload, while Nimbus JOSE + JWT classes are used for all other processing.</p><pre>import java.text.*;<br>import java.util.*;<br>import com.authlete.sd.*;<br>import com.nimbusds.jose.*;<br>import com.nimbusds.jose.crypto.*;<br>import com.nimbusds.jose.jwk.*;<br>import com.nimbusds.jose.jwk.gen.*;<br>import com.nimbusds.jwt.*;<br><br>public class SDJWTExample<br>{<br>    public static void main(String[] args) throws Exception<br>    {<br>        // Prepare selectively-disclosable claims as a list of Disclosures<br>        List&lt;Disclosure&gt; disclosures = buildDisclosures();<br><br>        // Prepare regular claims<br>        Map&lt;String, Object&gt; regularClaims = buildRegularClaims();<br><br>        // Generate a private key to sign the Issuer-signed JWT<br>        ECKey privateKey = generatePrivateKey();<br><br>        // Create the Issuer-signed JWT<br>        String issuerSignedJwt =<br>            buildIssuerSignedJwt(disclosures, regularClaims, privateKey);<br><br>        // Output the Issuer-signed JWT<br>        System.out.println(issuerSignedJwt);<br>    }<br><br>    private static List&lt;Disclosure&gt; buildDisclosures()<br>    {<br>        // List of Disclosures<br>        List&lt;Disclosure&gt; disclosures = new ArrayList&lt;&gt;();<br><br>        // Add a Disclosure representing the claim &quot;family_name&quot;:&quot;Kawasaki&quot;<br>        disclosures.add(new Disclosure(&quot;family_name&quot;, &quot;Kawasaki&quot;));<br><br>        // Add a Disclosure representing the claim &quot;given_name&quot;:&quot;Takahiko&quot;<br>        disclosures.add(new Disclosure(&quot;given_name&quot;, &quot;Takahiko&quot;));<br><br>        return disclosures;<br>    }<br><br>    private static Map&lt;String, Object&gt; buildRegularClaims()<br>    {<br>        // Map of &quot;claim name -&gt; claim value&quot;<br>        Map&lt;String, Object&gt; claims = new LinkedHashMap&lt;&gt;();<br><br>        // Add the claim &quot;nickname&quot;:&quot;Taka&quot;<br>        claims.put(&quot;nickname&quot;, &quot;Taka&quot;);<br><br>        // Add the &quot;vct&quot; claim according to the SD-JWT VC specification<br>        claims.put(&quot;vct&quot;, &quot;https://credentials.example.com/identity_credential&quot;);<br><br>        return claims;<br>    }<br><br>    private static ECKey generatePrivateKey() throws JOSEException<br>    {<br>        // Generate a private key for the ES256 algorithm<br>        return new ECKeyGenerator(Curve.P_256).generate();<br>    }<br><br>    private static String buildIssuerSignedJwt(<br>        List&lt;Disclosure&gt; disclosures, Map&lt;String, Object&gt; regularClaims, ECKey privateKey)<br>        throws JOSEException, ParseException<br>    {<br>        // Create the payload part of the Issuer-signed JWT<br>        Map&lt;String, Object&gt; claims = buildPayload(disclosures, regularClaims);<br><br>        // Create the unsigned Issuer-signed JWT<br>        SignedJWT jwt = buildJwt(claims);<br><br>        // Sign the Issuer-signed JWT<br>        signJwt(jwt, privateKey);<br><br>        // Serialize the Issuer-signed JWT<br>        String serialized = jwt.serialize();<br><br>        return serialized;<br>    }<br><br>    private static Map&lt;String, Object&gt; buildPayload(<br>        List&lt;Disclosure&gt; disclosures, Map&lt;String, Object&gt; regularClaims)<br>    {<br>        // Create an SDObjectBuilder instance to construct the payload<br>        // of the Issuer-signed JWT. Here we use the default constructor,<br>        // so the hash algorithm will be the default (SHA-256).<br>        SDObjectBuilder builder = new SDObjectBuilder();<br><br>        // Add selectively-disclosable claims<br>        disclosures.forEach(builder::putSDClaim);<br><br>        // Add regular claims<br>        regularClaims.forEach((k, v) -&gt; builder.putClaim(k, v));<br><br>        // Build the Map instance representing the payload.<br>        // This Map will include the &quot;_sd&quot; array.<br>        Map&lt;String, Object&gt; payload = builder.build();<br><br>        return payload;<br>    }<br><br>    private static SignedJWT buildJwt(Map&lt;String, Object&gt; payload)<br>        throws JOSEException, ParseException<br>    {<br>        // Create the header part of the Issuer-signed JWT<br>        //<br>        // The header will be {&quot;alg&quot;:&quot;ES256&quot;,&quot;typ&quot;:&quot;dc+sd-jwt&quot;}.<br>        // The &quot;typ&quot; value is defined in the SD-JWT VC specification.<br>        JWSHeader header =<br>            new JWSHeader.Builder(JWSAlgorithm.ES256)<br>                .type(new JOSEObjectType(&quot;dc+sd-jwt&quot;))<br>                .build();<br><br>        // Convert the Map payload to a JWTClaimsSet using<br>        // the Nimbus JOSE + JWT library<br>        JWTClaimsSet claimsSet = JWTClaimsSet.parse(payload);<br><br>        // Create the unsigned Issuer-signed JWT<br>        SignedJWT jwt = new SignedJWT(header, claimsSet);<br><br>        return jwt;<br>    }<br><br>    private static void signJwt(SignedJWT jwt, ECKey privateKey) throws JOSEException<br>    {<br>        // Create a signer to perform signing<br>        JWSSigner signer = new ECDSASigner(privateKey);<br><br>        // Sign the JWT<br>        jwt.sign(signer);<br>    }<br>}</pre><p>This program outputs an Issuer-signed JWT as follows:</p><pre>eyJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJuaWNrbmFtZSI6IlRha2EiLCJfc2QiOlsiVmZ5X1MxeEVLQTc4c3Fsa19MMWlHLTZ6NnVkcFpwZGl2di1BMFBaNjNMVSIsImZsRWFDVnVEUWxidV9feDV4Vndpb25iVTIzdUtFZ1pZdW9NZG90Nnp4Y3MiXSwidmN0IjoiaHR0cHM6Ly9jcmVkZW50aWFscy5leGFtcGxlLmNvbS9pZGVudGl0eV9jcmVkZW50aWFsIn0.mYX7hh1jOzQLS_NlUEVgTaPG0sijFSYXGLqANDPQFJD3DAyS-NZSNVmHn_iQWu3Pa9Ev6DNnwOyAPTe-Eg4uEg</pre><p>Decoding the header and payload of this Issuer-signed JWT using base64url yields the following JSONs:</p><pre>{<br>  &quot;typ&quot;: &quot;dc+sd-jwt&quot;,<br>  &quot;alg&quot;: &quot;ES256&quot;<br>}</pre><pre>{<br>  &quot;nickname&quot;: &quot;Taka&quot;,<br>  &quot;_sd&quot;: [<br>    &quot;Vfy_S1xEKA78sqlk_L1iG-6z6udpZpdivv-A0PZ63LU&quot;,<br>    &quot;flEaCVuDQlbu__x5xVwionbU23uKEgZYuoMdot6zxcs&quot;<br>  ],<br>  &quot;vct&quot;: &quot;https://credentials.example.com/identity_credential&quot;<br>}</pre><p>In the payload, the nickname and vct claims appear with their names and values as-is, but the family_name and given_name claims are not included. Instead, the _sd array contains the digest values corresponding to the family_name and given_name claims.</p><p>Since the original values cannot be derived from the digest values, this Issuer-signed JWT alone does not reveal the information &quot;family_name&quot;:&quot;Kawasaki&quot; or &quot;given_name&quot;:&quot;Takahiko&quot;.</p><h3>SD-JWT</h3><p>An SD-JWT consists of:</p><ul><li>An <strong>Issuer-signed JWT</strong></li><li>Zero or more <strong>Disclosures</strong></li><li>An optional <strong>Key Binding JWT</strong></li></ul><pre>&lt;Issuer-signed JWT&gt;~&lt;Disclosure 1&gt;~...~&lt;Disclosure N&gt;~[&lt;Key Binding JWT&gt;]</pre><p>The SDJWT class in the SD-JWT library represents an SD-JWT and provides two constructors corresponding to the components described above:</p><pre>SDJWT(String issuerSignedJwt, Collection&lt;Disclosure&gt; disclosures)<br>SDJWT(String issuerSignedJwt, Collection&lt;Disclosure&gt; disclosures, String keyBindingJwt)</pre><p>In these constructors, the first argument is the serialized Issuer-signed JWT, and the second argument is a list of Disclosures.</p><p><em>NOTE: The argument representing the Issuer-signed JWT has the type </em><em>String, not </em><em>SignedJWT from the Nimbus JOSE + JWT library. Using </em><em>String deliberately ensures that the </em><em>SDJWT class does not depend on any specific JWT library.</em></p><p>By modifying the sample program mentioned earlier as shown below, you can generate an SD-JWT and output its serialized form.</p><pre>      public static void main(String[] args) throws Exception<br>      {<br>          // Prepare selectively-disclosable claims as a list of Disclosures<br>          List&lt;Disclosure&gt; disclosures = buildDisclosures();<br><br>          // Prepare regular claims<br>          Map&lt;String, Object&gt; regularClaims = buildRegularClaims();<br><br>          // Generate a private key to sign the Issuer-signed JWT<br>          ECKey privateKey = generatePrivateKey();<br><br>          // Create the Issuer-signed JWT<br>          String issuerSignedJwt =<br>              buildIssuerSignedJwt(disclosures, regularClaims, privateKey);<br><br>-         // Output the Issuer-signed JWT<br>-         System.out.println(issuerSignedJwt);<br>+         // Create the SD-JWT<br>+         SDJWT sdjwt = new SDJWT(issuerSignedJwt, disclosures);<br>+<br>+         // Serialize the SD-JWT and output it<br>+         System.out.println(sdjwt);<br>      }</pre><p>When you run the modified program, it outputs an SD-JWT like the following:</p><pre>eyJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJuaWNrbmFtZSI6IlRha2EiLCJfc2QiOlsiQmxTRkNtLVJyLVc0UUZSV1pyb09keVNwV3BmUnVuTExGZDh4djB4VXJaVSIsIkk5Z2JrWGxNVEhHTThzTHNjenpVMDNlVEl2U1R2OWlnTlE5alpKLS1XYlkiXSwidmN0IjoiaHR0cHM6Ly9jcmVkZW50aWFscy5leGFtcGxlLmNvbS9pZGVudGl0eV9jcmVkZW50aWFsIn0.G365hmMY_Wp9vSVmifxKNXdVgxcXGuY_wevarIK95sgKFs6U8Me1a7WinSUVlQrWHDjT0AC9nM0GbYYV4hD6DQ~WyI5UFgwUXdUZXo3QlhOcjlkWVhpRVlnIiwiZmFtaWx5X25hbWUiLCJLYXdhc2FraSJd~WyJVTjByajNRTkJTeWJmSnNHY1NEYktBIiwiZ2l2ZW5fbmFtZSIsIlRha2FoaWtvIl0~</pre><p>You can create an SDJWT instance from its serialized form by using the parse(String) method of the SDJWT class.</p><pre>SDJWT sdjwt = SDJWT.parse(<br>    &quot;&quot;&quot;\<br>    eyJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJua\<br>    WNrbmFtZSI6IlRha2EiLCJfc2QiOlsiQmxTRkNtLVJyLVc0UUZ\<br>    SV1pyb09keVNwV3BmUnVuTExGZDh4djB4VXJaVSIsIkk5Z2JrW\<br>    GxNVEhHTThzTHNjenpVMDNlVEl2U1R2OWlnTlE5alpKLS1XYlk\<br>    iXSwidmN0IjoiaHR0cHM6Ly9jcmVkZW50aWFscy5leGFtcGxlL\<br>    mNvbS9pZGVudGl0eV9jcmVkZW50aWFsIn0.G365hmMY_Wp9vSV\<br>    mifxKNXdVgxcXGuY_wevarIK95sgKFs6U8Me1a7WinSUVlQrWH\<br>    DjT0AC9nM0GbYYV4hD6DQ~WyI5UFgwUXdUZXo3QlhOcjlkWVhp\<br>    RVlnIiwiZmFtaWx5X25hbWUiLCJLYXdhc2FraSJd~WyJVTjBya\<br>    jNRTkJTeWJmSnNHY1NEYktBIiwiZ2l2ZW5fbmFtZSIsIlRha2F\<br>    oaWtvIl0~\<br>    &quot;&quot;&quot;<br>    );</pre><h3>SDObjectDecoder</h3><p>You can recover the original claim names and values by computing the digest values of each Disclosure contained in the SD-JWT and matching them against the digest values in the payload of the Issuer-signed JWT.</p><p>Since the original claim values can themselves be JSON objects containing an _sd array, the digest matching process may need to be performed recursively. A JSON object representing an array element that contains only a single key &quot;...&quot; is also subject to this processing.</p><p>Considering additional checks such as the _sd_alg claim, this matching process is not particularly difficult, but writing the implementation from scratch each time can be tedious. For this reason, the SD-JWT library provides a utility class called SDObjectDecoder that performs this matching process.</p><p>By using one of the decode methods of SDObjectDecoder, you can execute the matching process (see the <a href="https://authlete.github.io/sd-jwt/com/authlete/sd/SDObjectDecoder.html">JavaDoc of </a><a href="https://authlete.github.io/sd-jwt/com/authlete/sd/SDObjectDecoder.html">SDObjectDecoder</a> for details).</p><p>The following sample program decodes the payload of the Issuer-signed JWT in an SD-JWT specified by the first argument and displays it in JSON format.</p><pre>import java.util.*;<br>import com.authlete.sd.*;<br>import com.google.gson.*;<br>import com.nimbusds.jwt.*;<br><br>public class DecodeExample<br>{<br>    public static void main(String[] args) throws Exception<br>    {<br>        // Parse the first argument provided on the command line as an SD-JWT<br>        SDJWT sdjwt = SDJWT.parse(args[0]);<br><br>        // Convert the Issuer-signed JWT represented as a String<br>        // to an instance of SignedJWT from the Nimbus JOSE + JWT library<br>        SignedJWT jwt = SignedJWT.parse(sdjwt.getCredentialJwt());<br><br>        // Convert the payload of the Issuer-signed JWT to a Map instance<br>        // In most cases, this Map contains the _sd array<br>        Map&lt;String, Object&gt; encodedPayload =<br>            jwt.getJWTClaimsSet().toJSONObject();<br><br>        // Extract the list of Disclosures contained in the SD-JWT<br>        List&lt;Disclosure&gt; disclosures = sdjwt.getDisclosures();<br><br>        // Create an SDObjectDecoder that will replace digest values<br>        // in the payload with their corresponding claims<br>        SDObjectDecoder decoder = new SDObjectDecoder();<br><br>        // Compute the digest value of each Disclosure, match it with<br>        // the digest values in the payload, and replace the digest<br>        // values with the original claims<br>        Map&lt;String, Object&gt; decodedPayload =<br>            decoder.decode(encodedPayload, disclosures);<br><br>        // Convert the Map instance to JSON<br>        String decodedPayloadJson =<br>            new GsonBuilder().setPrettyPrinting().create().toJson(decodedPayload);<br><br>        // Output the JSON<br>        System.out.println(decodedPayloadJson);<br>    }<br>}</pre><p>Assuming the shell variable SDJWT contains an SD-JWT:</p><pre>SDJWT=eyJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJuaWNrbmFtZSI6IlRha2EiLCJfc2QiOlsiQmxTRkNtLVJyLVc0UUZSV1pyb09keVNwV3BmUnVuTExGZDh4djB4VXJaVSIsIkk5Z2JrWGxNVEhHTThzTHNjenpVMDNlVEl2U1R2OWlnTlE5alpKLS1XYlkiXSwidmN0IjoiaHR0cHM6Ly9jcmVkZW50aWFscy5leGFtcGxlLmNvbS9pZGVudGl0eV9jcmVkZW50aWFsIn0.G365hmMY_Wp9vSVmifxKNXdVgxcXGuY_wevarIK95sgKFs6U8Me1a7WinSUVlQrWHDjT0AC9nM0GbYYV4hD6DQ~WyI5UFgwUXdUZXo3QlhOcjlkWVhpRVlnIiwiZmFtaWx5X25hbWUiLCJLYXdhc2FraSJd~WyJVTjByajNRTkJTeWJmSnNHY1NEYktBIiwiZ2l2ZW5fbmFtZSIsIlRha2FoaWtvIl0~</pre><p>Running the sample program with $SDJWT as the first argument:</p><pre>java -cp nimbus-jose-jwt.jar:sd-jwt.jar:gson.jar DecodeExample.java $SDJWT</pre><p>produces the following JSON output:</p><pre>{<br>  &quot;family_name&quot;: &quot;Kawasaki&quot;,<br>  &quot;given_name&quot;: &quot;Takahiko&quot;,<br>  &quot;vct&quot;: &quot;https://credentials.example.com/identity_credential&quot;,<br>  &quot;nickname&quot;: &quot;Taka&quot;<br>}</pre><h3>Verification of SD-JWT VCs/VPs</h3><p>Decoding the payload of an Issuer-signed JWT by matching it with Disclosures is straightforward using SDObjectDecoder. However, to trust the decoded content, you must verify the signature of the Issuer-signed JWT.</p><p>Additionally, if a Verifiable Presentation in SD-JWT format contains a Key Binding JWT (<a href="https://www.rfc-editor.org/rfc/rfc9901.html#section-4.3">RFC 9901 Section 4.3, Key Binding JWT</a>), you also need to verify the signature of that Key Binding JWT and check the sd_hash claim.</p><p>The <a href="https://github.com/authlete/sd-jwt/blob/main/src/test/java/com/authlete/sd/VerificationTest.java">VerificationTest.java</a> included in the SD-JWT library tests serves as a sample for both generating SD-JWT–formatted Verifiable Credentials / Verifiable Presentations and verifying their signatures. You can refer to it as needed.</p><h3>Other Topics</h3><p>There are additional features not covered in this article, such as the SDObjectEncoder class for making the contents of a Map instance recursively selectively disclosable, and the Disclosure.toArrayElement() method for generating Map instances representing selectively disclosable array elements. For more details, please refer to the SD-JWT library documentation (<a href="https://github.com/authlete/sd-jwt">README</a> / <a href="https://authlete.github.io/sd-jwt">JavaDoc</a>) or the source code as needed.</p><h3>Conclusion</h3><p>Authlete has contributed to the development of many standards, and the SD-JWT specification (<a href="https://www.rfc-editor.org/rfc/rfc9901.html">RFC 9901</a>) is just one example. If you are interested in working on cutting-edge industry standards, be sure to check out Authlete’s <a href="https://www.authlete.com/about#career">Careers</a> section!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=df1e43560aa9" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>