<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jérôme LELEU</title>
    <description>The latest articles on DEV Community by Jérôme LELEU (@jleleu).</description>
    <link>https://dev.to/jleleu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3857046%2F85b42614-37cd-4b19-b558-83ce9a555d0d.jpg</url>
      <title>DEV Community: Jérôme LELEU</title>
      <link>https://dev.to/jleleu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jleleu"/>
    <language>en</language>
    <item>
      <title>JWKS explained: what every developer should know</title>
      <dc:creator>Jérôme LELEU</dc:creator>
      <pubDate>Tue, 16 Jun 2026 13:32:03 +0000</pubDate>
      <link>https://dev.to/jleleu/jwks-explained-what-every-developer-should-know-1jcp</link>
      <guid>https://dev.to/jleleu/jwks-explained-what-every-developer-should-know-1jcp</guid>
      <description>&lt;p&gt;When it comes to security, certificates are used everywhere since the early days of the web.&lt;/p&gt;

&lt;p&gt;While storing them in PEM/DER format has always been complicated, things have become much easier with the modern JWKS (J for JSON) format.&lt;/p&gt;

&lt;p&gt;And you're probably already using JWKS without knowing it, every time you validate a JWT from Google, GitHub, or your identity provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  1) A word about cryptography
&lt;/h2&gt;

&lt;p&gt;We can use symmetric cryptography based on a secret.&lt;/p&gt;

&lt;p&gt;As this secret must be shared by both parties, this is not generally a very convenient solution.&lt;/p&gt;

&lt;p&gt;Or we can use asymmetric cryptography based on key pairs.&lt;/p&gt;

&lt;p&gt;In that case, there are two keys: a public one and a private one.&lt;/p&gt;

&lt;p&gt;Two mechanisms are available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the signature ensures that the sender is confirmed (the sender uses its private key to sign the message and the receiver can confirm that using the public key of the sender)&lt;/li&gt;
&lt;li&gt;the encryption protects the data itself (the sender uses the public key of the receiver to encrypt the data and only the receiver can read the data thanks to its private key).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both mechanisms are complementary and serve different purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2) In the past: SAML and XML
&lt;/h2&gt;

&lt;p&gt;Back when SAML was the main protocol and XML very popular, you generated certificates using the &lt;code&gt;openssl&lt;/code&gt; tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:4096 &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-days&lt;/span&gt; 365 &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-keyout&lt;/span&gt; private.key &lt;span class="nt"&gt;-out&lt;/span&gt; public.crt &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/CN=localhost"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It created two files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;private.key&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDdx8R3Y1Eyh69R
O8iACpe6MWJAUgMadWPt1VW2XGkrkvSBn9hY866VBt8wkH1uFmOAvvjwx55Tvu1K
...TRUNCATED...
ySD6rqvGLLxGkZoUGyuHt9D7B/FaBAMvjjgOSMYbHxYj0ncQioaVSpcUZIpTrHRo
jA1drmXT/LHPGeQgp/CJQ3Zf7qqavA==
-----END PRIVATE KEY-----
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;public.crt&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-----BEGIN CERTIFICATE-----
MIIFLjCCAxagAwIBAgIUOtBi9hdWAqh1sL8U7wS3ttXgg40wDQYJKoZIhvcNAQEL
BQAwITELMAkGA1UEBhMCRlIxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNjA1MTgx
...TRUNCATED...
po1DwOR88q6xAws/qM1+PxigbFRh4E8zUeVVF0vED+VxeCG0AwKDYawPjw5/9qfJ
qC8ewt6SVZmmdtMg2MK8Tdmzv0W+ciiYO21CF45Pa6YZVA==
-----END CERTIFICATE-----
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These raw contents were hard to manipulate.&lt;/p&gt;

&lt;p&gt;You could even generate a keystore (for Java) using the &lt;code&gt;keytool&lt;/code&gt; command line.&lt;/p&gt;

&lt;h2&gt;
  
  
  3) Modern ecosystem: OIDC and JSON
&lt;/h2&gt;

&lt;p&gt;Today, the OIDC protocol has somehow supplanted the SAML protocol and JSON has truly replaced the XML format.&lt;/p&gt;

&lt;p&gt;Everyone knows the JSON format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"key1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"value1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"key2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"value2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most people also know that a JSON Web Token (aka JWT) is a signed and/or encrypted JSON message.&lt;/p&gt;

&lt;p&gt;It comes as a string in three parts separated by dots, each part being base64 encoded: &lt;code&gt;part1.part2.part3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;part1&lt;/code&gt; is the header, &lt;code&gt;part2&lt;/code&gt; is the JSON itself (it can be encrypted) and &lt;code&gt;part3&lt;/code&gt; is the signature (it may not be signed).&lt;/p&gt;

&lt;p&gt;Let's take an example from &lt;code&gt;jwt.io&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.
KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have three parts which decode to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a header: &lt;code&gt;{ "alg": "HS256", "typ": "JWT" }&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a body: &lt;code&gt;{ "sub": "1234567890", "name": "John Doe", "admin": true, "iat": 1516239022 }&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a signature.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the encryption/signing of the JWTs is ensured by the public/private keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  4) JWK and JWKS
&lt;/h2&gt;

&lt;p&gt;Given the popularity of JSON, it was high time to find a better format than the PEM(/DER) format for certificates and what better format than JSON?&lt;/p&gt;

&lt;p&gt;So, JWK (for JSON Web Key) is the format to define a key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;kty&lt;/code&gt; property defines the type &lt;code&gt;RSA&lt;/code&gt;, &lt;code&gt;EC&lt;/code&gt;, ...&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;use&lt;/code&gt; property indicates if the key is used for signature ("sig") or encryption ("enc")&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;alg&lt;/code&gt; property defines the algorithm (it can be omitted)&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;kid&lt;/code&gt; property defines the name for the key and this is a very cool feature to distinguish between keys&lt;/li&gt;
&lt;li&gt;the specific &lt;code&gt;n&lt;/code&gt; and &lt;code&gt;e&lt;/code&gt; properties for RSA, the specific &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; properties for Elliptic Curve.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, you can have this JWK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RSA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"e"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AQAB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"use"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyname"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"n"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2moVQ...2aq7Q"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a JWKS, the S stands for Set (not for the plural), is a set of JWKs = keys listed in an array defined by the &lt;code&gt;keys&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;For example, the JWKS of our previous JWK is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keys"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RSA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"e"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AQAB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"use"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyname"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"n"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2moVQ...2aq7Q"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is super easy and much clearer than the PEM format given that you now have an identifier for your key, the use of your key, an algorithm, etc.&lt;/p&gt;

&lt;p&gt;Instead of a block certificate, you have several separate pieces of information.&lt;/p&gt;

&lt;h2&gt;
  
  
  5) Easier but...
&lt;/h2&gt;

&lt;p&gt;Despite the more pleasant format, there is no magic, there are pitfalls to avoid (like with regular certificates).&lt;/p&gt;

&lt;p&gt;Plain certificates were painful and no one would take them lightly. Yet, this nicer JWKS format of the keys must not make you forget that you deal with security.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Trap #1&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you still need to take care of the rotation/revocation of the keys in your JWKS: add a JWK, remove an old one, ... things don't happen by themselves (hopefully).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Trap #2&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While JWKS exposed on the internet contain public keys, private/internal JWKS can contain private keys.&lt;/p&gt;

&lt;p&gt;For example, this is the JWKS of the private key for our previous public JWK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"keys"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"p"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"-4uskk...sMm98"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"RSA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"q"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"3kg3S...FgErM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"d"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"UT_QS...l1LYw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"AQAB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"use"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"keyname"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"qi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Lp-0T...lo4afg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"dp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"xcakA...18JHE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"dq"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"JByJV...XmqiP8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2moVQ...2aq7Q"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should notice that there are more information for private keys and especially you always find the &lt;code&gt;d&lt;/code&gt; property in a private key.&lt;/p&gt;

&lt;p&gt;This is really important as you must always be able to distinguish between a public key and a private key.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Because the golden rule remains: you must never publicly disclose a private key.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Trap #3&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is even a new trap with the &lt;code&gt;alg&lt;/code&gt; property: this is absolutely not a security constraint, it is only a recommendation.&lt;/p&gt;

&lt;p&gt;So you must not be confused by this value and only trust what you have really configured and applied in your code.&lt;/p&gt;

&lt;p&gt;This is exactly like for the JWT header where the &lt;code&gt;alg&lt;/code&gt; key is only informative: trusting it could expose you to the &lt;em&gt;algorithm confusion&lt;/em&gt; attack.&lt;/p&gt;

&lt;p&gt;You must always rely on what you actually defined and used for encryption/signature. You must never rely on what is provided to you from the outside.&lt;/p&gt;

&lt;p&gt;JWKS is a modern format to store/manage keys you will really enjoy,&lt;br&gt;but you must never forget the good practices regardless!&lt;/p&gt;

&lt;p&gt;See how pac4j deals with JWKS in the &lt;a href="https://www.pac4j.org/docs/clients/openid-connect-config.html#c-private_key_jwt" rel="noopener noreferrer"&gt;OIDC private_key_jwt authentication method&lt;/a&gt; or in the &lt;a href="https://www.pac4j.org/docs/clients/openid-connect-federation.html#1-federation-endpoint" rel="noopener noreferrer"&gt;OpenID Federation&lt;/a&gt;...&lt;/p&gt;

</description>
      <category>api</category>
      <category>beginners</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>More OpenID Federation with pac4j and Connect2id</title>
      <dc:creator>Jérôme LELEU</dc:creator>
      <pubDate>Mon, 18 May 2026 10:06:26 +0000</pubDate>
      <link>https://dev.to/jleleu/more-openid-federation-with-pac4j-and-connect2id-4np8</link>
      <guid>https://dev.to/jleleu/more-openid-federation-with-pac4j-and-connect2id-4np8</guid>
      <description>&lt;p&gt;I strongly recommend that you read the first article about the &lt;a href="https://www.pac4j.org/blog/openid_federation_with_pac4j_and_connect2id.html" rel="noopener noreferrer"&gt;OpenID Federation protocol&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This new article dives deeper into the OpenID Federation support in pac4j and Connect2id.&lt;/p&gt;

&lt;p&gt;You should download and use the latest versions of both software (at least version 6.5.1 for pac4j).&lt;/p&gt;

&lt;h2&gt;
  
  
  1) Let's log in (again)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  a) Calling the login page
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pac4j.org%2Fimg%2Fblog%2F2026_05%2Fbefore_login.png" class="article-body-image-wrapper"&gt;&lt;img alt="Before login" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pac4j.org%2Fimg%2Fblog%2F2026_05%2Fbefore_login.png" width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we have previously seen, the first login generates several logs on both sides (client = RP = pac4j + server = OP = connect2id).&lt;/p&gt;

&lt;p&gt;Several HTTP calls are required to check the JWKS and the entity statements and establish the trust chains.&lt;/p&gt;

&lt;p&gt;This could be a performance issue if these HTTP calls were made for each login attempt, though on the second try, the logs are much less verbose before displaying the login page:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The pac4j logs:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG o.p.o.r.OidcRedirectionActionBuilder     : Request Object claim names: [iss, aud, iat, exp, jti, scope, response_type,
 redirect_uri, state, code_challenge_method, client_id, code_challenge, response_mode]
DEBUG o.p.o.r.OidcRedirectionActionBuilder     : Authz parameter names: [response_type, request, client_id, scope]
DEBUG o.p.o.r.OidcRedirectionActionBuilder     : Authentication request URL: http://127.0.0.1:8080/c2id-login
 ?response_type=code&amp;amp;request=eyJr...hULhwg&amp;amp;client_id=http%3A%2F%2Flocalhost%3A8081&amp;amp;scope=openid%20profile%20email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The Connect2id logs:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO AUTHZ-SESSION - [OP2101] Created new auth session: sid=FKEttMylh3MVBAtu59F7CXx5m4BTGXZ2_DKowRh_8eg
 client_id=http://localhost:8081 scope=[openid, profile, email]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully, trust chains have been cached depending on the expiration time and the whole plumbing has not been triggered a second time.&lt;/p&gt;

&lt;h3&gt;
  
  
  b) After typing in the login and password
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pac4j.org%2Fimg%2Fblog%2F2026_05%2Fafter_login.png" class="article-body-image-wrapper"&gt;&lt;img alt="After login" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pac4j.org%2Fimg%2Fblog%2F2026_05%2Fafter_login.png" width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a successful login, we get the following logs:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;On the Connect2id side:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO SESSION-STORE - [SS0201] Added new session: sub=alice ctx=web sid_key=MhNnqmimFgizG5ALmnp-tg
INFO AUTHZ-SESSION - [OP2103] Created new consent session: sid=FKEttMylh3MVBAtu59F7CXx5m4BTGXZ2_DKowRh_8eg subject=alice
 client_id=http://localhost:8081
INFO AUTHZ-SESSION - [OP2108] Created authZ response: subject=alice client_id=http://localhost:8081 response_type=[code]
INFO TOKEN - HTTP POST request: ip=127.0.0.1 path=/c2id/token
INFO TOKEN - [OP6204] Authenticated: client_id=http://localhost:8081 method=private_key_jwt client_auth_id=NgAiEAADRhMrDiZp
INFO AUTHZ-STORE - [AS0280] Issued access token: sub=alice act=null client_id=http://localhost:8081 scope=[openid]
INFO TOKEN - [OP6225] Success response: client_id=http://localhost:8081 grant=code tokens=[access,id]
INFO USERINFO - HTTP GET request: ip=127.0.0.1 path=/c2id/userinfo
INFO AUTHZ-STORE - [AS0213] Inspected valid SELF_CONTAINED Bearer access token: sub=alice act=null client_id
 =http://localhost:8081 iat=1775213922: eyJraWQiOiJQUlJ6Iiwid...
INFO USERINFO - [OP7307] Received valid UserInfo request: sub=alice claims=null ia_id=aac191d2-dcc5-4837-8545-692e204bcc07
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;em&gt;fairly&lt;/em&gt; obvious:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A POST call is performed on the &lt;code&gt;/c2id/token&lt;/code&gt; endpoint using the &lt;code&gt;private_key_jwt&lt;/code&gt; client authentication method (this is what we have configured on the pac4j side)&lt;/li&gt;
&lt;li&gt;A GET call is performed on the &lt;code&gt;/c2id/userinfo&lt;/code&gt; endpoint using the &lt;code&gt;access_token&lt;/code&gt; as bearer (= HTTP header).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the regular OIDC login process &lt;u&gt;even if the flow has started in a federation way&lt;/u&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;On the pac4j side:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG o.p.o.c.e.OidcCredentialsExtractor       : Authentication response successful
DEBUG o.p.o.c.e.OidcCredentialsExtractor       : Request state: d508c0f0ad/response state: d508c0f0ad
DEBUG org.pac4j.oidc.client.OidcClient         : clean authentication attempt from session
DEBUG o.p.o.c.authenticator.OidcAuthenticator  : Token response: status=200, content={"access_token":"eyJ...bng","token_type":"Bearer","expires_in":600}
DEBUG o.p.o.c.authenticator.OidcAuthenticator  : Token response successful
DEBUG org.pac4j.oidc.client.OidcClient         : clean authentication attempt from session (second call)
DEBUG org.pac4j.oidc.client.OidcClient         : Credentials validation took: 32 ms
DEBUG org.pac4j.oidc.client.OidcClient         : credentials : OidcCredentials(code=sjtox4...NQw, accessToken=...
DEBUG org.pac4j.oidc.profile.OidcProfile       : adding =&amp;gt; key: access_token / value: eyJh...MDB9 / class java.lang.String
DEBUG org.pac4j.oidc.profile.OidcProfile       : adding =&amp;gt; key: expiration / value: 1775214522541 / class java.lang.Long
DEBUG org.pac4j.oidc.profile.OidcProfile       : adding =&amp;gt; key: id_token / value: eyJ...bng / class java.lang.String
DEBUG o.p.oidc.profile.creator.TokenValidator  : Trying IDToken validator, issuer: http://127.0.0.1:8080/c2id, type: null, JWS:
DEBUG o.p.oidc.profile.creator.TokenValidator  : Validated: {"iss":"http:\/\/127.0.0.1:8080\/c2id","sub":"alice",
 "aud":"http:\/\/localhost:8081","exp":1775214522,"iat":1775213922,"amr":["pwd"]}
DEBUG o.p.o.p.creator.OidcProfileCreator       : User info response: status=200, content={"sub":"alice","groups":["admin","audit"]}
DEBUG o.p.oidc.profile.OidcProfileDefinition   : converted to =&amp;gt; key: sub / value: alice / class java.lang.String
DEBUG org.pac4j.oidc.profile.OidcProfile       : adding =&amp;gt; key: sub / value: alice / class java.lang.String
...
DEBUG org.pac4j.oidc.profile.OidcProfile       : adding =&amp;gt; key: token_expiration_advance / value: 0 / class java.lang.Integer
DEBUG org.pac4j.oidc.client.OidcClient         : profile: Optional[OidcProfile(super=AbstractJwtProfile(super=CommonProfile(
 super=BasicUserProfile(logger=Logger[org.pac4j.oidc.profile.OidcProfile], id=alice, attributes={access_token=eyJ...MDB9,
 token_expiration_advance=0, sub=alice, aud=[http://localhost:8081], amr=[pwd], id_token=eyJr...Hsbng,
 iss=http://127.0.0.1:8080/c2id, groups=[admin, audit], expiration=1775214522541, exp=Fri Apr 03 13:08:42 CEST 2026,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs are straightforward on the pac4j side as well: we see the successful authentication, the token and the userprofile calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  c) After the login process
&lt;/h3&gt;

&lt;p&gt;Even though we’re not doing anything, new logs keep appearing for the Connect2id server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO AUTHZ-STORE - [AS0228] Revoking multiple authzs: client_id=http://localhost:8081
INFO CLIENT-REG - [OP5184] Deleted client: client_id=http://localhost:8081 num_revoked_authz=1
INFO FED-REG - [OP8041] Reaped 1 expired federation clients
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These logs are related to the fact that we have performed an automatic registration. The logs indicate that the temporarily created client is deleted.&lt;/p&gt;

&lt;p&gt;Indeed, as Connect2id does not know the pac4j client, it has &lt;strong&gt;temporarily&lt;/strong&gt; registered this client and after some time, the registered client is cleaned.&lt;/p&gt;

&lt;p&gt;While this is a very convenient mechanism, it can impact server performance.&lt;/p&gt;

&lt;p&gt;Therefore, it could be useful to consider explicitly and permanently registering our OIDC pac4j client.&lt;/p&gt;

&lt;h2&gt;
  
  
  2) Let's log in with explicit registration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  a) The client identifier
&lt;/h3&gt;

&lt;p&gt;And this is a feature supported by the OpenID Federation protocol:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the explicit registration of the OIDC client.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This must be of course supported by the OIDC server and this is the case of the Connect2id server.&lt;/p&gt;

&lt;p&gt;pac4j supports both modes depending on the OIDC server, so the configuration must only be updated on the Connect2id server.&lt;/p&gt;

&lt;p&gt;Stop the server (&lt;code&gt;tomcat/bin/shutdown.sh&lt;/code&gt;), edit the &lt;code&gt;tomcat/webapps/c2id/WEB-INF/oidcProvider.properties&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;op.federation.clientRegistrationTypes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;explicit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and restart the server (&lt;code&gt;tomcat/bin/startup.sh&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;On the pac4j side:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG o.p.o.m.r.FederationClientRegister       : Registration endpoint exists and only explicit registration by OP (and RP)
 -&amp;gt; performing explicit registration
 INFO .f.e.DefaultEntityConfigurationGenerator : Generating entity configuration for: http://localhost:8081
DEBUG o.p.o.m.r.FederationClientRegister       : Received response registration: eyJraW...mkaMxZQ
 WARN o.p.o.m.r.FederationClientRegister       : /!\ ================================================
 WARN o.p.o.m.r.FederationClientRegister       : /!\ Explicit registration of the client 'http://localhost:8081' returns
  id: [t4j746kwjax6s]. This information won't be repeated. You MUST add this value to your configuration before the next
   application startup!
 WARN o.p.o.m.r.FederationClientRegister       : /!\ ================================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;On the Connect2id side:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO FED-REG - [OP8014] Registered entity http://localhost:8081 as explicit client with client_id=xkqolxvshcjv6 exp=1783005293
INFO FED-REG - [OP8019] Explicit registration response statement for entity http://localhost:8081: {sub=http://localhost:8081,
 aud=[http://localhost:8081], metadata={openid_relying_party={client_registration_types=[explicit, automatic],
 token_endpoint_auth_signing_alg=RS256, grant_types=[authorization_code], jwks={keys=[{kty=RSA, e=AQAB, use=sig, kid=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The explicit registration is duly taken into account by Connect2id which generates a specific &lt;code&gt;client_id&lt;/code&gt; for the OIDC client, returns it to pac4j to be displayed in its logs.&lt;/p&gt;

&lt;p&gt;Let's follow the instruction given in the logs and add this &lt;code&gt;client_id&lt;/code&gt; in the pac4j configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OidcConfiguration&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// the new clientId!&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setClientId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"xkqolxvshcjv6"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rpJwks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRpJwks&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;rpJwks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setJwksPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"file:./metadata/rpjwks.jwks"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;rpJwks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setKid&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"defaultjwks0426"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setClientAuthenticationMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClientAuthenticationMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PRIVATE_KEY_JWT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;privateKeyJwtConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrivateKeyJwtClientAuthnMethodConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rpJwks&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPrivateKeyJWTClientAuthnMethodConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;privateKeyJwtConfig&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRequestObjectSigningAlgorithm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JWSAlgorithm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RS256&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;federation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFederation&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTargetOp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://127.0.0.1:8080/c2id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;trust&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OidcTrustAnchorProperties&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;trust&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setIssuer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8081/trustanchor"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;trust&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setJwksPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"classpath:trustanchor.jwks"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTrustAnchors&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trust&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getJwks&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setJwksPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"file:./metadata/oidcfede.jwks"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getJwks&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setKid&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mykeyoidcfede26"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContactName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"New RP test"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContactEmails&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jerome@casinthecloud.com"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setEntityId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8081"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseUri&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/callback"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OidcClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the complete configuration for reference (and not only the added &lt;code&gt;client_id&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;We restart the Spring Boot application and try a new login process.&lt;/p&gt;

&lt;p&gt;This time, no registration happens and Connect2id directly recognizes the provided &lt;code&gt;client_id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO AUTHZ-SESSION - [OP2101] Created new auth session: sid=reJ...58w client_id=xkqolxvshcjv6 scope=[openid, profile, email]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  b) The client secret
&lt;/h3&gt;

&lt;p&gt;At this point in the article, you may wonder why we only have a &lt;code&gt;client_id&lt;/code&gt; and no &lt;code&gt;client_secret&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In fact, we don't need a secret as we use the &lt;code&gt;private_key_jwt&lt;/code&gt; client authentication method: the credential is the private key, not the secret.&lt;/p&gt;

&lt;p&gt;As this pac4j configuration is revealed in its entity statement, the Connect2id server is aware of that setting and, &lt;strong&gt;accordingly&lt;/strong&gt;, decides to only return a &lt;code&gt;client_id&lt;/code&gt; for this OIDC client.&lt;/p&gt;

&lt;p&gt;Let's go further and replace this configuration in pac4j:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setClientAuthenticationMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClientAuthenticationMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PRIVATE_KEY_JWT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;privateKeyJwtConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrivateKeyJwtClientAuthnMethodConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rpJwks&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPrivateKeyJWTClientAuthnMethodConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;privateKeyJwtConfig&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setClientAuthenticationMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClientAuthenticationMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CLIENT_SECRET_BASIC&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to use the &lt;code&gt;client_secret_basic&lt;/code&gt; authentication (and not the &lt;code&gt;private_key_jwt&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;We also remove the previous &lt;code&gt;client_id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setClientId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"xkqolxvshcjv6"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and completely change the contact name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContactName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"New RP test"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the Spring Boot application and try to log in.&lt;/p&gt;

&lt;p&gt;This time, we call the Connect2id server with explicit registration and no configured client id/secret and a &lt;code&gt;client_secret_basic&lt;/code&gt; authentication method.&lt;/p&gt;

&lt;p&gt;And we get a new error from pac4j:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;org.pac4j.oidc.exceptions.OidcException: Client secret export file is required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This seems definitely a weird one, but it's not! Let me explain: the received &lt;code&gt;client_id&lt;/code&gt; is output in the logs, though it would not be safe to output the &lt;code&gt;client_secret&lt;/code&gt; in the logs as well.&lt;/p&gt;

&lt;p&gt;So the received &lt;code&gt;client_secret&lt;/code&gt; is planned to be saved on the disk, on a file defined by the &lt;code&gt;secretExportFile&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;Let's define it in the configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSecretExportFile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./mysecret.tmp"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and try again. It works!&lt;/p&gt;

&lt;p&gt;The generated &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; have been added on-the-fly to the OIDC configuration and have been used to perform the &lt;code&gt;client_secret_basic&lt;/code&gt; authentication method.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;client_id&lt;/code&gt; is in the logs: &lt;code&gt;Explicit registration of the client 'http://localhost:8081' returns id: [wzcyln5hdtmck]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;client_secret&lt;/code&gt; is in the defined file: &lt;code&gt;The received secret has been saved into the file: ./mysecret.tmp&lt;/code&gt;. Its value is: &lt;code&gt;U9UaF99rUopAXnWqXqkWBj_RsOZXfM1Efs67N4KweHo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These seem to be the right settings as the login process has worked, but we'd like to check that on the Connect2id side.&lt;/p&gt;

&lt;p&gt;Let's seek in the Connect2id configuration file &lt;code&gt;oidcProvider.properties&lt;/code&gt; for the property: &lt;code&gt;op.reg.apiAccessTokenSHA256&lt;/code&gt;. I find:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# Evaluation note: Use token value ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
&lt;/span&gt;&lt;span class="py"&gt;op.reg.apiAccessTokenSHA256&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;cca68b8b82bcf0b96cb826199429e50cd95a042f8e8891d1ac56ab135d096633&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try to use the Connect2id API to list the existing clients with this key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET http://127.0.0.1:8080/c2id/clients &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We get two clients:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"client_registration_types"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"explicit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"automatic"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"grant_types"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"authorization_code"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"jwks"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"keys"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"RSA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"AQAB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"use"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"defaultjwks0426"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2moV...aq7Q"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"subject_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"application_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"web"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"registration_client_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http:&lt;/span&gt;&lt;span class="se"&gt;\/\/&lt;/span&gt;&lt;span class="s2"&gt;127.0.0.1:8080&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="s2"&gt;c2id&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="s2"&gt;clients&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="s2"&gt;wzcyln5hdtmck"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"redirect_uris"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"http:&lt;/span&gt;&lt;span class="se"&gt;\/\/&lt;/span&gt;&lt;span class="s2"&gt;localhost:8081&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="s2"&gt;callback?client_name=OidcClient"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"registration_access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0QKFCVfBe5PqVFwgjEaRousXHKEa9My0-IMMMCTPB20.YSxpLHI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"token_endpoint_auth_method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"client_secret_basic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"wzcyln5hdtmck"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"client_secret_expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"request_object_signing_alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"client_id_issued_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1775836027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"client_secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"U9UaF99rUopAXnWqXqkWBj_RsOZXfM1Efs67N4KweHo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"client_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"New RP test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"contacts"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"jerome@casinthecloud.com"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"response_types"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"code"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id_token_signed_response_alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"token_endpoint_auth_signing_alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"grant_types"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"authorization_code"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"jwks"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"keys"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"RSA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"AQAB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"use"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"defaultjwks0426"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2moV...aq7Q"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"subject_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"application_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"web"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"registration_client_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http:&lt;/span&gt;&lt;span class="se"&gt;\/\/&lt;/span&gt;&lt;span class="s2"&gt;127.0.0.1:8080&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="s2"&gt;c2id&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="s2"&gt;clients&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="s2"&gt;xkqolxvshcjv6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"redirect_uris"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"http:&lt;/span&gt;&lt;span class="se"&gt;\/\/&lt;/span&gt;&lt;span class="s2"&gt;localhost:8081&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="s2"&gt;callback?client_name=OidcClient"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"registration_access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"7ilSWZGsH4wOyehETCNJQz0My8NO-6efLiV1ED2HcN0.YSxpLHI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"token_endpoint_auth_method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"private_key_jwt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"xkqolxvshcjv6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"request_object_signing_alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"client_id_issued_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1775747499&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"client_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"C2ID Test RP (Localhost)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"contacts"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"jerome@casinthecloud.com"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"response_types"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"code"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id_token_signed_response_alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second one has only the right &lt;code&gt;client_id&lt;/code&gt;, no &lt;code&gt;client_secret&lt;/code&gt; and is defined with &lt;code&gt;private_key_jwt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The first one has the right &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; and is defined with &lt;code&gt;client_secret_basic&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Notice the appropriate &lt;code&gt;client_name&lt;/code&gt; property as well.&lt;/p&gt;

&lt;p&gt;The Connect2id configuration perfectly matches what was received by pac4j (not that I had any doubts 😉)&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;magic of the federation&lt;/strong&gt; continues:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The pac4j RP and the Connect2id OP only know and rely on the trust anchor, they don't know each other.&lt;/p&gt;

&lt;p&gt;But nonetheless the &lt;b&gt;RP has been able to definitely register itself on the OP!&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>security</category>
      <category>java</category>
    </item>
    <item>
      <title>OpenID Federation with pac4j and Connect2id</title>
      <dc:creator>Jérôme LELEU</dc:creator>
      <pubDate>Thu, 02 Apr 2026 07:24:34 +0000</pubDate>
      <link>https://dev.to/jleleu/openid-federation-with-pac4j-and-connect2id-5f5c</link>
      <guid>https://dev.to/jleleu/openid-federation-with-pac4j-and-connect2id-5f5c</guid>
      <description>&lt;p&gt;Since version 6.4.0, &lt;a href="https://www.pac4j.org" rel="noopener noreferrer"&gt;pac4j&lt;/a&gt; supports the &lt;a href="https://openid.net/specs/openid-federation-1_0.html" rel="noopener noreferrer"&gt;OpenID (Connect) Federation specification&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The OpenID Connect (in short, OIDC) support in pac4j (&lt;code&gt;pac4j-oidc&lt;/code&gt; module) is strongly based on the Nimbus libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.nimbusds&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;oauth2-oidc-sdk&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.nimbusds&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;nimbus-jose-jwt&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are great Open Source libraries developed by the Connect2id team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://connect2id.com/products/nimbus-jose-jwt" rel="noopener noreferrer"&gt;https://connect2id.com/products/nimbus-jose-jwt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk" rel="noopener noreferrer"&gt;https://connect2id.com/products/nimbus-oauth-openid-connect-sdk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And as they also developed an OIDC server, let's test the pac4j OpenID Federation support with it!&lt;/p&gt;

&lt;h2&gt;
  
  
  1) Components
&lt;/h2&gt;

&lt;p&gt;In this article, we will focus on the setup of this particular configuration more than entering into the details and options of each component.&lt;/p&gt;

&lt;p&gt;We need 3 components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a client, which is called the Relying Party (RP) in OIDC, and we will use pac4j&lt;/li&gt;
&lt;li&gt;a server, which is called the OpenID Provider (OP) in OIDC, and we will use the Connect2id server&lt;/li&gt;
&lt;li&gt;a trust anchor (TA in short) and we will create a simulated one to simplify the whole installation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  a) RP = pac4j
&lt;/h3&gt;

&lt;p&gt;In the pac4j ecosystem, Spring Boot is the most popular web stack so let's use the &lt;code&gt;spring-webmvc-pac4j&lt;/code&gt; implementation in action in this simple demo: &lt;a href="https://github.com/pac4j/simple-spring-boot-pac4j-demos/tree/oidc/src/main/java/org/pac4j/demos" rel="noopener noreferrer"&gt;https://github.com/pac4j/simple-spring-boot-pac4j-demos/tree/oidc/src/main/java/org/pac4j/demos&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;SpringBootDemo&lt;/code&gt; class runs the Spring Boot demo&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;SecurityConfig&lt;/code&gt; class defines the security configuration (OIDC + URL protection)&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;Application&lt;/code&gt; class is a simple controller with two URLs: one public and the other one protected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We need the following dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-web&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.pac4j&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-webmvc-pac4j&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;8.0.2&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.pac4j&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;pac4j-oidc&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;6.4.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  b) OP = Connect2id
&lt;/h3&gt;

&lt;p&gt;To make things easy, let's use the "Quick start" of the Connect2id server: &lt;a href="https://connect2id.com/products/server/docs/quick-start" rel="noopener noreferrer"&gt;https://connect2id.com/products/server/docs/quick-start&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Download the ZIP and unzip it. You will get a &lt;code&gt;connect2id-server-19.8&lt;/code&gt; directory or something similar.&lt;/p&gt;

&lt;p&gt;In that directory, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run the server: &lt;code&gt;tomcat/bin/startup.sh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;stop the server: &lt;code&gt;tomcat/bin/shutdown.sh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;change its configuration in the &lt;code&gt;tomcat/webapps/c2id/WEB-INF/oidcProvider.properties&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;check the logs in the &lt;code&gt;tomcat/logs/c2id-server.log&lt;/code&gt; file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  c) TA is simulated
&lt;/h3&gt;

&lt;p&gt;The trust anchor is a core component in our installation: it plays the role of the certificate authority.&lt;/p&gt;

&lt;p&gt;We could use some specific existing software, but to make things easier, we will simulate it without entering into the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  2) Configuration
&lt;/h2&gt;

&lt;p&gt;Now that we have our 3 components, let's configure them to work together via the OpenID Federation protocol.&lt;/p&gt;

&lt;p&gt;So far, when using the OpenID Connect protocol, you always need to define your client (&lt;code&gt;client_id&lt;/code&gt; + credentials + &lt;code&gt;redirect_uri&lt;/code&gt;) at the server level to be able to perform the authentication process.&lt;/p&gt;

&lt;p&gt;The OpenID Federation introduces the idea of trust and chain of trust between components to allow authenticating without registering upfront/at all.&lt;/p&gt;

&lt;p&gt;And each component (entry) in the federation exposes its metadata on the new &lt;code&gt;.well-known/openid-federation&lt;/code&gt; endpoint. A dedicated JWKS ensures security, similar to an X.509 certificate but without its complexity/difficulty.&lt;/p&gt;

&lt;h2&gt;
  
  
  a) pac4j
&lt;/h2&gt;

&lt;p&gt;As the Connect2id server runs by default on port 8080, we'll move our pac4j application to port 8081 via this configuration in the &lt;code&gt;application.properties&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;server.port&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;8081&lt;/span&gt;
&lt;span class="py"&gt;app.base-url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8081&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We update the &lt;code&gt;SecurityConfig&lt;/code&gt; class to change the configuration for the federation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;org.pac4j.demos&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.nimbusds.jose.JWSAlgorithm&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.pac4j.core.config.Config&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.pac4j.oidc.client.OidcClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.pac4j.oidc.config.OidcConfiguration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.pac4j.oidc.config.method.PrivateKeyJwtClientAuthnMethodConfig&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.pac4j.oidc.federation.config.OidcTrustAnchorProperties&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.pac4j.springframework.config.Pac4jSecurityConfig&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.beans.factory.annotation.Value&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.servlet.config.annotation.InterceptorRegistry&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecurityConfig&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Pac4jSecurityConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${app.base-url:http://localhost:8080}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;baseUri&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OidcConfiguration&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rpJwks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRpJwks&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;rpJwks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setJwksPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"file:./metadata/rpjwks.jwks"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;rpJwks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setKid&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"defaultjwks0426"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setClientAuthenticationMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClientAuthenticationMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PRIVATE_KEY_JWT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;privateKeyJwtConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrivateKeyJwtClientAuthnMethodConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rpJwks&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPrivateKeyJWTClientAuthnMethodConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;privateKeyJwtConfig&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRequestObjectSigningAlgorithm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JWSAlgorithm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RS256&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;federation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFederation&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTargetOp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://127.0.0.1:8080/c2id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;trust&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OidcTrustAnchorProperties&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;trust&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setIssuer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8081/trustanchor"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;trust&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setJwksPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"classpath:trustanchor.jwks"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTrustAnchors&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trust&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getJwks&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setJwksPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"file:./metadata/oidcfede.jwks"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getJwks&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setKid&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mykeyoidcfede26"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContactName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"C2ID Test RP (Localhost)"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContactEmails&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jerome@casinthecloud.com"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;federation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setEntityId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8081"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseUri&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/callback"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OidcClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;addInterceptors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;InterceptorRegistry&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;addSecurity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"OidcClient"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;addPathPatterns&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/protected/**"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This new configuration needs explanations!&lt;/p&gt;

&lt;p&gt;Notice that we don't have any &lt;code&gt;dicsoveryURI&lt;/code&gt;, nor any &lt;code&gt;clientId&lt;/code&gt; or any credentials!&lt;/p&gt;

&lt;p&gt;We need two distinct and separate JWKS for our setup here: one for the federation (to sign our entity statement) and one for the &lt;code&gt;private_key_jwt&lt;/code&gt; client authentication (when calling the token endpoint). &lt;strong&gt;You should never use the same JWKS for both!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The source code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rpJwks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRpJwks&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;rpJwks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setJwksPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"file:./metadata/rpjwks.jwks"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;rpJwks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setKid&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"defaultjwks0426"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setClientAuthenticationMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClientAuthenticationMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PRIVATE_KEY_JWT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;privateKeyJwtConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrivateKeyJwtClientAuthnMethodConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rpJwks&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPrivateKeyJWTClientAuthnMethodConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;privateKeyJwtConfig&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;defines the JWKS used for the &lt;code&gt;private_key_jwt&lt;/code&gt; client authentication. This will create a key named &lt;code&gt;defaultjwks0426&lt;/code&gt; and save it as a JWKS in the &lt;code&gt;metadata/rpjwks.jwks&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;We use the Request Object signing configuration as the federation requires more security:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRequestObjectSigningAlgorithm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JWSAlgorithm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RS256&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JWKS used for signing here is the previous one defined in the &lt;code&gt;rpJwks&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;In federation, you must define your trust anchor(s) (the root authority). So we do that with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final var trust = new OidcTrustAnchorProperties();
trust.setIssuer("http://localhost:8081/trustanchor");
trust.setJwksPath("classpath:trustanchor.jwks");
federation.getTrustAnchors().add(trust);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It assumes the simulated TA is available at the &lt;code&gt;http://localhost:8081/trustanchor&lt;/code&gt; URL with a JWKS also in the classpath (this is in the entity statement, but we rely on the one downloaded in the classpath).&lt;/p&gt;

&lt;p&gt;The target OP (= the server we want to use for authentication) is of course the Connect2id and is defined by: &lt;code&gt;federation.setTargetOp("http://127.0.0.1:8080/c2id")&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, the entity identifier of our RP pac4j client is &lt;code&gt;http://localhost:8081&lt;/code&gt; (its own base URL) thanks to &lt;code&gt;federation.setEntityId("http://localhost:8081")&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As we don't have a &lt;code&gt;clientId&lt;/code&gt;, the &lt;code&gt;entityId&lt;/code&gt; is used to identify the RP.&lt;/p&gt;

&lt;p&gt;It also means we must expose a dedicated OpenID federation endpoint (in the &lt;code&gt;Application&lt;/code&gt; class):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Autowired&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/.well-known/openid-federation"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultEntityConfigurationGenerator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CONTENT_TYPE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@ResponseBody&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;oidcFederation&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;HttpAction&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oidcClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OidcClient&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClients&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;findClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OidcClient"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;oidcClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConfiguration&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getFederation&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getEntityConfigurationGenerator&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;generateEntityStatement&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  b) Connect2id
&lt;/h2&gt;

&lt;p&gt;The Connect2Id server is available on the &lt;code&gt;http://127.0.0.1:8080/c2id&lt;/code&gt; URL.&lt;/p&gt;

&lt;p&gt;To enable federation, in the &lt;code&gt;tomcat/webapps/c2id/WEB-INF/oidcProvider.properties&lt;/code&gt; file, you need to change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;### Federation ###
&lt;/span&gt;
&lt;span class="c"&gt;# Enables / disables OpenID Federation 1.0. Disabled by default.
&lt;/span&gt;&lt;span class="py"&gt;op.federation.enable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# The configured trust anchors. Leave blank if none.
&lt;/span&gt;&lt;span class="py"&gt;op.federation.trustAnchors.1&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8081/trustanchor&lt;/span&gt;

&lt;span class="c"&gt;# Trust anchors or intermediate entities that may issue an entity statement
# about this OpenID provider. Leave blank if none.
&lt;/span&gt;&lt;span class="py"&gt;op.federation.authorityHints.1&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8081/trustanchor&lt;/span&gt;
&lt;span class="py"&gt;op.federation.authorityHints.2&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="py"&gt;op.federation.authorityHints.3&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;

&lt;span class="c"&gt;# The enabled OpenID Federation 1.0 client registration types. The default
# value is none (for deployments that use manual client registration only).
#
# Supported OpenID Federation 1.0 client registration types:
#
#     * explicit
#     * automatic
#
&lt;/span&gt;&lt;span class="py"&gt;op.federation.clientRegistrationTypes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;automatic&lt;/span&gt;

&lt;span class="c"&gt;# The enabled methods for authenticating OpenID Federation 1.0 automatic client
# registration requests at the OAuth 2.0 authorisation endpoint.
#
# Supported methods:
#
#     * request_object
#
&lt;/span&gt;&lt;span class="py"&gt;op.federation.autoClientAuthMethods.ar&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;request_object&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parameters are quite obvious: &lt;code&gt;op.federation.enable=true&lt;/code&gt; to enable the federation.&lt;/p&gt;

&lt;p&gt;The trust anchor is defined twice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;op.federation.trustAnchors.1=http://localhost:8081/trustanchor
op.federation.authorityHints.1=http://localhost:8081/trustanchor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we use the automatic registration mode (the simplest way): &lt;code&gt;op.federation.clientRegistrationTypes=automatic&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Stop and start again the Connect2id server. You should see this welcome page on &lt;code&gt;http://127.0.0.1:8080/c2id&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pac4j.org%2Fimg%2Fblog%2F2026_04%2Fc2id_welcome.png" class="article-body-image-wrapper"&gt;&lt;img alt="Connect2id welcome page" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pac4j.org%2Fimg%2Fblog%2F2026_04%2Fc2id_welcome.png" width="792" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meaning the federation is properly enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  c) Simulated TA
&lt;/h2&gt;

&lt;p&gt;For the simulated trust anchor, let's not enter into the details to limit the size of this article.&lt;/p&gt;

&lt;p&gt;We expose a few endpoints to simulate its behavior and the contract it must respect (in the &lt;code&gt;Application&lt;/code&gt; class):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/trustanchor/.well-known/openid-federation"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultEntityConfigurationGenerator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CONTENT_TYPE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@ResponseBody&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;trustAnchorWellKnown&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"eyJraWQiOiJ0YS1rZX...vBabK4L897BynKoyihoUHj4Q"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/trustanchor/fetch"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultEntityConfigurationGenerator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CONTENT_TYPE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@ResponseBody&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;trustAnchorFetch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sub"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// OP&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"eyJraWQiOiJ0YS1rZX...ldEVZd49QyZRnENUCP4hOMCWiQ"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// RP&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"eyJraWQiOiJ0YS1rZX...n94DBmi3m158dbEO2ZWyM_9YdWtxjerAl1Zh-bgzA6H17tRGlk-oYd-7g"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And add the &lt;code&gt;trustanchor.jwks&lt;/code&gt; to the &lt;code&gt;src/main/resources&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This simulated TA depends on your Connect2id server keys so you won't be able to reproduce it locally until we move to a real trust anchor. We will do this in a future article.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3) Let's log in
&lt;/h2&gt;

&lt;p&gt;Now we run our pac4j Spring Boot RP and our Connect2id OP server.&lt;/p&gt;

&lt;p&gt;Let's add more logs on the pac4j side via: &lt;code&gt;logging.level.org.pac4j.oidc=DEBUG&lt;/code&gt; (in the &lt;code&gt;application.properties&lt;/code&gt; file).&lt;/p&gt;

&lt;p&gt;Open the &lt;code&gt;http://localhost:8081/&lt;/code&gt; URL in your browser and click on the &lt;code&gt;Protected area&lt;/code&gt; link:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pac4j.org%2Fimg%2Fblog%2F2026_04%2Fpac4j_protected.png" class="article-body-image-wrapper"&gt;&lt;img alt="pac4j welcome page" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pac4j.org%2Fimg%2Fblog%2F2026_04%2Fpac4j_protected.png" width="180" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ta-da! It works: the Connect2id login page is displayed and we can log in with the default &lt;code&gt;alice&lt;/code&gt;/ &lt;code&gt;secret&lt;/code&gt; user/password:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pac4j.org%2Fimg%2Fblog%2F2026_04%2Fc2id_login.png" class="article-body-image-wrapper"&gt;&lt;img alt="Connect2id login page" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.pac4j.org%2Fimg%2Fblog%2F2026_04%2Fc2id_login.png" width="657" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pac4j logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG o.p.o.m.OidcFederationOpMetadataResolver : Blocking load of the provider metadata via federation
 INFO o.p.o.m.chain.FederationChainResolver    : Resolving federation chain for RP: http://localhost:8081
DEBUG o.p.o.m.chain.FederationChainResolver    : Loaded 1 trust anchor(s)
DEBUG o.p.o.m.chain.FederationChainResolver    : OP target issuer: http://127.0.0.1:8080/c2id
DEBUG o.p.o.m.chain.FederationChainResolver    : OP chain: com.nimbusds.openid.connect.sdk.federation.trust.TrustChain@14c16574
DEBUG o.p.o.m.chain.FederationChainResolver    : rawMetadataJson: {"request_parameter_supported":true,"pushed_authorization_...
DEBUG o.p.o.m.chain.FederationChainResolver    : combinedPolicy: {}
DEBUG o.p.o.m.chain.FederationChainResolver    : resolvedJson: {"request_parameter_supported":true,"pushed_authorization_...
DEBUG o.p.o.m.r.FederationClientRegister       : ClientID is not defined
DEBUG o.p.o.m.r.FederationClientRegister       : Automatic registration by OP (supported by RP) -&amp;gt;
setting clientId as entityId for further operation
DEBUG o.p.o.r.OidcRedirectionActionBuilder     : Algorithm used for request object signing: RS256
DEBUG o.p.o.r.OidcRedirectionActionBuilder     : Request Object claim names: [iss, aud, iat, exp, jti, scope,
response_type, redirect_uri, state, code_challenge_method, client_id, code_challenge, response_mode]
DEBUG o.p.o.r.OidcRedirectionActionBuilder     : Authz parameter names: [response_type, request, client_id, scope]
DEBUG o.p.o.r.OidcRedirectionActionBuilder     : Authentication request URL: http://127.0.0.1:8080/c2id-login?response_type=code
&amp;amp;request=eyJraW...KLjkrVw&amp;amp;client_id=http%3A%2F%2Flocalhost%3A8081&amp;amp;scope=openid%20profile%20email
 INFO .f.e.DefaultEntityConfigurationGenerator : Generating entity configuration for: http://localhost:8081
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Connect2id logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO FED-REG - [OP8025] Resolved 1 trust chains (automatic client):
INFO FED-REG - [OP8026] Trust chain [1] anchor (automatic client): http://localhost:8081/trustanchor
INFO FED-REG - [OP8026] Trust chain [1][1] entity (automatic client): http://localhost:8081
INFO FED-REG - [OP8026] Trust chain [1][1] statement (automatic client): {"sub":"http:\/\/localhost:8081","metadata":{"openid_relying_party":...
INFO FED-REG - [OP8026] Trust chain [1][2] entity (automatic client): http://localhost:8081
INFO FED-REG - [OP8026] Trust chain [1][2] statement (automatic client): {"sub":"http:\/\/localhost:8081","metadata":{"openid_relying_party":...
INFO FED-REG - [OP8013] Selected trust chain for entity http://localhost:8081 (automatic client) with anchor
http://localhost:8081/trustanchor and exp 2026-06-25T20:52:26.000+0200
INFO FED-REG - [OP8018] Received automatic registration request from http://localhost:8081 with authorities [http://localhost:8081/trustanchor]
INFO FED-REG - [OP8051] Effective metadata RP policy for entity http://localhost:8081: {}
INFO FED-REG - [OP8014] Registered entity http://localhost:8081 as automatic client with client_id=http://localhost:8081 exp=1782413546
INFO AUTHZ-SESSION - [OP2101] Created new auth session: sid=0r61vms5vYcodERRslQNMT0QqFRQsjw2Jr33YL4de5s client_id=http://localhost:8081 scope=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without going too deep in the logs, we can see that both sides are checking federation endpoints and entity statements to establish the trust chain.&lt;/p&gt;

&lt;p&gt;So here is the &lt;strong&gt;magic of federation&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The pac4j RP and the Connect2id OP only know and rely on the trust anchor, they don't know each other.&lt;br&gt;
But nonetheless the RP can perform the authentication process on the OP!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>security</category>
      <category>java</category>
    </item>
  </channel>
</rss>
