This feature will partially implement JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants.

Section 2.2. Using JWTs for Client Authentication will be the focus for this feature implementation.

NOTE: This ticket addresses client-side support only.

Related #6881 #6053

Comment From: ghost

Cool. Authenticating with Azure AD using a certificate uses this flow, afaics. This feature will help us executing that flow cleanly. Thx.

Comment From: thomas-corte

I believe that this would also provide better support for "Sign In With Apple", as Apple uses a non-static client secret, which in their case must be the string representation of a JWT (seemingly compliant with the JWT profile covered in the issue) with an expiration date of at most 6 months in the future (see https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens).

Comment From: jgrandja

@thomas-corte Thanks for pointing that out. Indeed, Apple does support private_key_jwt client authentication so this feature will be needed.

FYI, we are targeting this for 5.5 but we're dependent on merging #9208 first, which we are getting close.

Comment From: thomas-corte

Not sure whether this helps (as #9208 seems to aim at using the Nimbus libraries), but for comparison, I was able to successfully create a working secret for Apple using this code (which utilizes BouncyCastle and the io.jsonwebtoken library):

import java.util.Date; import java.util.HashMap; import java.util.Map;

import java.io.Reader; import java.io.StringReader;

import java.security.Key; import java.security.PrivateKey;

import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;

import org.bouncycastle.openssl.PEMParser;

import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;

...

JwtBuilder jwtBuilder = Jwts.builder ();

String keyBase64 = "-----BEGIN PRIVATE KEY-----\n" +
  "<base64 encoded private key data from Apple>" +
  "-----END PRIVATE KEY-----";

final Reader pemReader = new StringReader (keyBase64);
final PEMParser pemParser = new PEMParser (pemReader);
final JcaPEMKeyConverter converter = new JcaPEMKeyConverter ();

final PrivateKeyInfo object = (PrivateKeyInfo) pemParser.readObject ();
final PrivateKey pKey = converter.getPrivateKey (object);

Map<String, Object> headers = new HashMap<> ();
headers.put ("kid", "WTXXXXXX"); // Key ID from Apple
headers.put ("alg", "ES256");
jwtBuilder.setHeader (headers);

String clientSecret = jwtBuilder.
  setIssuer ("KVXXXXXXXX"). // Team ID from Apple
  setIssuedAt (new Date (System.currentTimeMillis ())).
  setExpiration (new Date (System.currentTimeMillis () + 180l * 86_400_000l)). // 6 months max
  setAudience ("https://appleid.apple.com").
  setSubject ("com.my.app.name").
  signWith (SignatureAlgorithm.ES256, pKey).compact ();

Comment From: jgrandja

Thanks for the sample @thomas-corte. We will be using Nimbus for implementation.

Comment From: willladislaw

Any updates on this?

Comment From: jgrandja

@willladislaw I'm starting work on this next week.

Comment From: jgrandja

@roelal @thomas-corte @willladislaw Please see #9520.

It would be greatly appreciated if you can test out the initial implementation with the provider(s) you are using. Please let me know if the new API provides the flexibility you require for your use case(s).

FYI, I need to get this merged before April 12 for 5.5.0-RC1, so your prompt response would be a huge help. Thank you.

Comment From: erlendfg

@roelal @thomas-corte @willladislaw Please see #9520.

It would be greatly appreciated if you can test out the initial implementation with the provider(s) you are using.

I tried to download the sources from your branch and build it with Gradle, but I'm not having sufficient access to the required dependencies (Received status code 401 from server: Unauthorized), probably related to this issue: https://stackoverflow.com/questions/64839144/got-an-error-when-building-spring-security-from-source

Comment From: Robinyo

@jgrandja

I would like to help out with testing support for 'Private Key JWT Client Authentication'.

The Australian Government's Trusted Digital Identity Framework requires Relying Party's to use Private Key JWT Client Authentication.

Ref: - https://www.dta.gov.au/our-projects/digital-identity/trusted-digital-identity-framework - https://relying-party-technical-integration-guide.apps.y.cld.gov.au/

Comment From: jgrandja

Thanks for your offer @Robinyo. Feel free to test the implementation in #9520 with the Australian Government's Identity system.

Let me know how it goes. Thanks.

Comment From: willladislaw

Maven repository unavailable. How do I add this dependency? (Spring Boot)

Comment From: jgrandja

@willladislaw Spring Security 5.5.0-RC1 is only available in https://repo.spring.io. After 5.5.0 goes GA then it's available in Maven Central.

See the Spring Boot reference on how to setup the Spring Maven repo.

Comment From: willladislaw

How long before 5.5.0 GA (approx)? No other way to add it?

Comment From: erlendfg

I'm eager to try this out, but I didn't get any further after I added the following to my config class (and removed clientSecret): .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)

Should I customize any Spring Security class and sign the JWT by using Nimbus? In that case, in which class? Or is it possible to add the certificate along with the private key to an existing Spring Security class?

Comment From: jgrandja

@willladislaw See milestone dates

Comment From: jgrandja

@erlendfg See the main comment in gh-9520 as it points to a couple of tests that demonstrate configuration / usage.

Comment From: marcerik

I tried out this, but did not get it to work, seems like x5t:base64EncodedThumprintHere and typ:jwt parameters inside the client_assertion are not sent to to the Authentication provider at all.

Comment From: alan-czajkowski

@thomas-corte do you have a working example of how to configure the application.yaml and how to use a non-static client secret using the private key?

Comment From: thomas-corte

No, just what I wrote here: https://github.com/spring-projects/spring-security/issues/8175#issuecomment-784957091

Comment From: alan-czajkowski

@thomas-corte so are you manually "fixing" the client-secret every few months? or do you generate it on application start-up, and assume that the application will be restarted in less than 6 months, therefore generating on start-up is "good enough"? or do you generate it somehow during application run-time and dynamically inject it to Spring Boot's OAuth2 library before it sends the request to Apple?

Comment From: thomas-corte

No, this is not production code, I just wanted to provide a working example for generating a suitable secret to the maintainers of the related Spring code. In our own project, we moved to using KeyCloak and its identity broker functionality (though I’m not sure whether it supports apple as an OpenID provider to be honest).

Comment From: alan-czajkowski

if anybody has a working Spring Boot example that dynamically generates a (non-static) "client secret" for providers such as Apple, then please reference it here

Comment From: Robinyo

Private Key JWT Client Authentication using nimbus-jose-jwt.

gist: https://gist.github.com/Robinyo/cc90c191be74ca173fb483199b3efceb

Comment From: alan-czajkowski

@Robinyo i'm looking for a solution that's compatible with this configuration:

application.yaml

spring:
  security:
    oauth2:
      client:
        registration:
          apple:
            client-name: "Apple"
            client-id: "..."

            # client secret is a signed JWT that must be auto-generated at either application start-up or during application run-time
            client-secret: "..."

            redirect-uri: "{baseUrl}/security/auth/oauth2/callback/{registrationId}"
            authorization-grant-type: "authorization_code"
            client-authentication-method: "post"
            scope:
              - "email"
              - "openid"
              - "name"
        provider:
          apple:
            authorization-uri: "https://appleid.apple.com/auth/authorize?response_mode=form_post"
            token-uri: "https://appleid.apple.com/auth/token"
            jwk-set-uri: "https://appleid.apple.com/auth/keys"
            user-name-attribute: "sub"
...

Comment From: Robinyo

Spring Boot and Keycloak example: application.yml

Comment From: sabareeshkkanan

@alan-czajkowski were you able to find an working example with dynamic client secret ?

Comment From: alan-czajkowski

@sabareeshkkanan no, I was not able to find a working example using a dynamic client secret ... so I created my own: https://gitlab.com/openease/java-microservice-example/-/blob/master/service/www/src/main/java/com/openease/service/www/WwwWebApplication.java

let me know if that works for you

Comment From: alexsyd

So, is Apple Authentication supported? Is there any documentation/examples? Where should I put the private key, teamId etc? There is ClientAuthenticationMethod("private_key_jwt") in https://github.com/spring-projects/spring-security/blob/6.0.x/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ClientAuthenticationMethod.java is it working? how do I use this type of authentication method?

Comment From: alan-czajkowski

@alexsyd this is an outdated example (I need to update it), but it gives you an idea of how you need to do Apple Sign-in, unfortunately Apple makes it incredibly difficult relative to everybody else (Google, Facebook, etc.) https://gitlab.com/openease/java-microservice-example

the main thing you need to do is create the client secret (the JWT) before the Spring Boot application starts, so that you can do some magic injection into the application.yaml, and then start the application, I've documented it pretty well: https://gitlab.com/openease/java-microservice-example/-/blob/master/service/www/src/main/java/com/openease/service/www/WwwWebApplication.java?ref_type=heads#L47

Comment From: alan-czajkowski

it would be helpful if Spring Boot could just implement this (painful) use-case by Apple, and do the client secret (JWT) generation, if the sign-in partner (like Apple) requires it