unable to get access_token, Refresh token using client_assertion_type(urn:ietf:params:oauth:client-assertion-type:jwt-bearer) and client_assertion Spring Security Unable to get access_token, Refresh token using client_assertion in Spring oauth2 Server

To Reproduce 1. get authorization_code using valid use name password 2. try to get access_token, Refresh token using client_assertion_type(urn:ietf:params:oauth:client-assertion-type:jwt-bearer) and client_assertion 3. getting error as per print screen. { "error": "invalid_client" }

Expected behavior should be able to get access_token, Refresh token using client_assertion_type and client_assertion

Sample @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.csrf().disable().cors(Customizer.withDefaults()).formLogin().loginPage("/oauth2/login").permitAll(); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults()); http.exceptionHandling().accessDeniedHandler(accessDeniedHandler()); return http.build(); } @Bean public RegisteredClientRepository registeredClientRepository() { ClientSettings clientSettings = ClientSettings.builder().requireAuthorizationConsent(false) .requireProofKey(false).build();

    RegisteredClient registeredClient1 = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientSettings(clientSettings).clientId(clientModel.getClientId())
            .clientSecret(passwordEncoder.encode(clientModel.getClientSecret()))
            .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            .authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .tokenSettings(TokenSettings.builder()
                    .accessTokenTimeToLive(Duration.ofMinutes(clientModel.getAccessTokenValidityMinutes()))
                    .refreshTokenTimeToLive(Duration.ofMinutes(clientModel.getRefreshTokenValidityMinutes()))
                    .build())
            .redirectUri(clientModel.getRedirectUri()).scope(OidcScopes.OPENID).scope("articles.read").build();

    return new InMemoryRegisteredClientRepository(registeredClient1);
}

dependencies { implementation 'org.springframework.boot:spring-boot-starter-parent:2.7.8' implementation 'org.springframework.boot:spring-boot-starter-security:2.7.8' implementation 'org.springframework.boot:spring-boot-starter-web:2.7.8' implementation 'org.springframework.security:spring-security-oauth2-authorization-server:0.4.0' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'com.oracle.database.jdbc:ojdbc8:12.2.0.1' implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.5.4' implementation 'org.springframework.boot:spring-boot-configuration-processor:2.7.8' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' }

A link to a GitHub repository with a minimal, reproducible sample.

Comment From: LepakshV

complete configuration is below - complete configuration is below - @Configuration @Import(OAuth2AuthorizationServerConfiguration.class) public class AuthorizationServerConfiguration { Logger logger = LoggerFactory.getLogger(AuthorizationServerConfiguration.class); private String providerSettingsIssuer = null; private String clientId = null; @Autowired private BCryptPasswordEncoder passwordEncoder;

@Autowired
private ClientDetailService clientDetailService;

@Autowired
ConfigurableEnvironment configurableEnvironment;

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http.csrf().disable().cors(Customizer.withDefaults()).formLogin().loginPage("/oauth2/login").permitAll();
    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());
    http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
    return http.build();

}

@Bean
public AccessDeniedHandler accessDeniedHandler() {
    return new CustomAccessDeniedHandler();
}

@Bean
public RegisteredClientRepository registeredClientRepository() {
    ClientSettings clientSettings = ClientSettings.builder().requireAuthorizationConsent(false)
            .requireProofKey(false).build();

    logger.info("Client details...");
    clientId = configurableEnvironment.getProperty("OAUTH_CLIENTID");
    logger.info("clientId:= " + clientId);

    ClientModel clientModel = clientDetailService.findByClientId(clientId);
    logger.info(clientModel.getClientSecret());
    logger.info(clientModel.getAccessTokenValidityMinutes().toString());
    logger.info(clientModel.getRefreshTokenValidityMinutes().toString());
    logger.info(clientModel.getRedirectUri());
    RegisteredClient registeredClient1 = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientSettings(clientSettings).clientId(clientModel.getClientId())
            .clientSecret(passwordEncoder.encode(clientModel.getClientSecret()))
            .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            .authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .tokenSettings(TokenSettings.builder()
                    .accessTokenTimeToLive(Duration.ofMinutes(clientModel.getAccessTokenValidityMinutes()))
                    .refreshTokenTimeToLive(Duration.ofMinutes(clientModel.getRefreshTokenValidityMinutes()))
                    .build())
            .redirectUri(clientModel.getRedirectUri()).scope(OidcScopes.OPENID).scope("articles.read").build();


    return new InMemoryRegisteredClientRepository(registeredClient1);
}

@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
    return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}

@Bean
public JWKSource<SecurityContext> jwkSource() throws NoSuchAlgorithmException {
    RSAKey rsaKey = generateRsa();
    JWKSet jwkSet = new JWKSet(rsaKey);

    return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}

private static RSAKey generateRsa() throws NoSuchAlgorithmException {
    KeyPair keyPair = generateRsaKey();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

    return new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();
}

private static KeyPair generateRsaKey() throws NoSuchAlgorithmException {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    keyPairGenerator.initialize(2048);

    return keyPairGenerator.generateKeyPair();
}

@Bean
public AuthorizationServerSettings authorizationServerSettings() {
    providerSettingsIssuer = configurableEnvironment.getProperty("OAUTH_ISSUER");
    return AuthorizationServerSettings.builder().issuer(providerSettingsIssuer).build();
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
        throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
}

@Bean
public DefaultAuthenticationEventPublisher authenticationEventPublisher() {
    return new DefaultAuthenticationEventPublisher();
}

}

Comment From: jgrandja

@LepakshV

Thanks for getting in touch, but it feels like this is a question that would be better suited to Stack Overflow. We prefer to use GitHub issues only for bugs and enhancements. Feel free to update this issue with a link to the re-posted question (so that other people can find it) or add a minimal sample that reproduces this issue if you feel this is a genuine bug.

Please see this comment as it provides a link to a branch for a working sample of JWT client authentication.

Comment From: LepakshV

@jgrandja As per your advice, I have used the same example given in the branch https://github.com/jgrandja/spring-authorization-server/tree/jwt-client-authn/samples/default-authorizationserver I get same error "error": "invalid_client". It will be great if you may share working sample, to generate client assertion also for testing.

Comment From: jgrandja

@LepakshV I just tried the Messages sample in the branch and it works as-is.

This commit applies changes to default-authorizationserver and messages-client samples so you need to run both along with messages-resource to see it working together.

Comment From: LepakshV

@jgrandja I am running both default-authorizationserver and messages-client samples projects. Below are clarification needed -

  1. Can we use public key from client certificate given instead of setting jwkSetUrl("http://localhost:8080/jwks") like this?
  2. I am getting invalid_client error in postman as per the print screen in shared above. It may be due to client assertion created is wrong by me. could you please let me know, how you created client_assertion ? client_assertion created by me is : eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJpZCIsImlhdCI6MTY3NTMyMjYxNiwic3ViIjoicHJvZmluY2giLCJpc3MiOiJwcm9maW5jaCIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjkwMDAvIiwiZXhwIjoxNjc1NDIyNjE2fQ.tFdYKSRtDk0HdT7k8B9R3O4X_zDFkZpN3KRZ3VsF_hE decoded client_assertion is: { "alg": "HS256" } { "jti": "id", "iat": 1675322616, "sub": "profinch", "iss": "profinch", "aud": "https://localhost:9000/", "exp": 1675422616 }

Comment From: LepakshV

@jgrandja I also tried to generate client_assertion like below using PKCS12, still I get invalid client error. Need your help to resolve this issue. Looks like authorization server is not compatible with certificate generated RSAkey. I am badly stuck here. InputStream fm = new FileInputStream(pathPKCS12);
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(fm, pwdPKCS12.toCharArray()); Key key = keystore.getKey(keystore.aliases().nextElement(), pwdPKCS12.toCharArray()); Certificate cert = keystore.getCertificate(keystore.aliases().nextElement()); PublicKey publicKey = cert.getPublicKey(); KeyPair keyPair = new KeyPair(publicKey, (PrivateKey) key);

    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();

    Instant now = Instant.now();
    //The JWT signature algorithm we will be using to sign the token
    SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RS256;

    String jwt= Jwts.builder()
                .setAudience("https://localhost:9000")
                .setIssuedAt(Date.from(now))
                .setExpiration(Date.from(now.plus(5L, ChronoUnit.DAYS)))
                .setIssuer("profinch")
                .setSubject("profinch")
                .setId(UUID.randomUUID().toString())
                .signWith(signatureAlgorithm, rsaPrivateKey)
                .compact();
    System.out.println(jwt);

Also in auth-server and jwkSetUrl I am using below logic- @Bean public JWKSource jwkSource() throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, JOSEException { RSAKey rsaKey = generateRsaFromCertificate(); JWKSet jwkSet = new JWKSet(rsaKey); return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); }

@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
    return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}

public RSAKey generateRsaFromCertificate() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
    String pathPKCS12 = configurableEnvironment.getProperty("OAUTH_PKCS12_PATH");
    String pwdPKCS12 = configurableEnvironment.getProperty("OAUTH_PKCS12_PWD");

// String pathPKCS12 = "classpath:keystore/mercury.p12"; // String pwdPKCS12 = "mercury";

    File file = ResourceUtils.getFile(pathPKCS12);
    InputStream fm = new FileInputStream(file);

    KeyStore keystore = KeyStore.getInstance("PKCS12");     
    keystore.load(fm, pwdPKCS12.toCharArray());
    Key key = keystore.getKey(keystore.aliases().nextElement(), pwdPKCS12.toCharArray());
    Certificate cert = keystore.getCertificate(keystore.aliases().nextElement());
    PublicKey publicKey = cert.getPublicKey();
    KeyPair keyPair = new KeyPair(publicKey, (PrivateKey) key);

    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();

    fm.close();

    // @formatter:off
    return new RSAKey.Builder(rsaPublicKey)
            .privateKey(rsaPrivateKey)
            .keyID(UUID.randomUUID().toString())
            .build();
    // @formatter:on
}

error is below - Spring Security Unable to get access_token, Refresh token using client_assertion in Spring oauth2 Server

post man request x-www-form-urlencoded is - client_assertion_type:urn:ietf:params:oauth:client-assertion-type:jwt-bearer client_assertion:eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJodHRwczovL2xvY2FsaG9zdDo5MDAwIiwiaWF0IjoxNjc1NTY3ODE3LCJleHAiOjE2NzU5OTk4MTcsImlzcyI6InByb2ZpbmNoIiwic3ViIjoicHJvZmluY2giLCJqdGkiOiIzODMwN2U1My1iYTU3LTQyMGUtOTExZi1lZDkzMmQ0ZGZmMDQifQ.cy4SOSCYa1s24UHosXwhskhUl3bIag_ZXnY7XKVY8SjLO65YLYOT8oHKbKhsAx_l47m7Tk3LBKIYYDOnORgfWCXDxyYdiTB-OKwTcPg6oHQu4323f38Wyb_4gOt56CGJnu45HOhnYNIll9VhvH_TJcFcSWzp4G5XTakA_ZfvdybhcB3hq8vxyd6_HStz24QxKFYUZZEC70vOCgExfNW6mMGEuL0bRHy6SnaJ1TfyjJD_3Mz9VKGdNry7Dr2cfQ9y1xro_LdAU3oZ97YQLeSStB-lOo--r7f4sUAYdX6ahrJwm8AgD6GugQqO-LGB15T0xdXGqXJrED2iC1Vr1BgblA