400 bad request for JWT Bearer Token flow for machine to machine authentication

Hi,

I am trying to test the machine to machine okta authentication for a backend service.I created a service app using Okta inegrator account for the REST API endpoint that I need to authenticate with Okta.I chose Public /Private key for client authentication and generated the public/private keys from okta admin console.Now when am testing the flow by writing a standalone springboot web app and calling the /token endpoint am getting 400 bad request invalid client.I have verified the signed JWT by decoding the JWT and everything looks good.I have added the default access policy as well in okta admin console and selected this service app under assigned to clients but still does not work.Any inputs or suggestions please to make this work?The KID,client ID,sub and aud everything looks good.Not sure where am going wrong.Please help.

I am using Spring Rest Template to make the http POST call to /token endpoint to get the access token.

RestTemplate restTemplate = new RestTemplate();

HttpHeaders headers = new HttpHeaders();

headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

MultiValueMap<String, String> body = new LinkedMultiValueMap<>();

body.add(“grant_type”, “urn:ietf:params:oauth:grant-type:jwt-bearer”);

body.add(“assertion”, assertion);

body.add(“scope”, “openid”);..My Token URL is https://integrator-XXXXXX.okta.com/oauth2/default/v1/token–Have redacted the actual for security

Are you getting a more specific error description back beyond invalid_client? Usually a more helpful error will be returned, for example, telling you if something in the request is formatted incorrectly or that the assertion has expired.

Hi Andrea,

Thanks for the response.I do not get any additional infomrmtion back from okta while calling the token endpoint.Is there anything I need to add to get more info from the /token endpoint?

Looks like you are using urn:ietf:params:oauth:grant-type:jwt-bearer grant type which is typically used for token exchange flows.

Are you trying to get an access token directly using an API service app? If so you have to use client_credentials grant type.

For example, this guide illustrates how this can be done to access Okta management API endpoints.

curl --location --request POST ‘https://{yourOktaDomain}/oauth2/default/v1/token’
–header ‘Accept: application/json’
–header ‘Content-Type: application/x-www-form-urlencoded’
–data-urlencode ‘grant_type=client_credentials’
–data-urlencode ‘scope=myapi.read’
–data-urlencode ‘client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer’
–data-urlencode ‘client_assertion=eyJhbGciOiJSU…tHQ6ggOnrG-ZFRSkZc8Pw’

1 Like

Hi,

I have configured service app in okta admin console using API service option as I want to set up machine to machine okta auth.I have selected public/private key and not client credentials for public keys.I have generated the key pair in okta admin console and I got the private key in JWK format.Now keeping that private key in my client that is springboot web app I created a signed JWT (assertion) and trying to make a call to okta /token endpoint to get an access token so that I can get authenticated.Hence I chose JWT bearer token as grant type and not client credentials.Please correct me if am wrong.

When you select Public key / Private key instead of Client secret for Client authentication field, you are changing the authentication not the grant type. Grant types will have Client Credentials and Token Exchange.

For your use case as described, you might have to use client_credentials grant type as I stated in my previous response. My example request accommodates you choosing Public key / Private key by adding client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearerheader.

You are receiving invalid client error due to the fact that your token call does not include the right authentication which will contain the client ID.

1 Like

I tried with client_credentials but still same issue..Please find the error org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request on POST request for “https://integrator-XXXXXX.okta.com/oauth2/default/v1/token”: “{“errorCode”:“invalid_client”,“errorSummary”:“A client_id must be provided in the request.”,“errorLink”:“invalid_client”,“errorId”:“oaeCx_lJfNTQYiY6PmzfEOAJw”,“errorCauses”:}”..change I made is MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add(“grant_type”, “urn:ietf:params:oauth:grant-type:client_credentials”);
body.add(“assertion”, assertion);
body.add(“scope”, “openid”);

What does the payload for your assertion look like?

Also, did you generate a new JWT (to use as the assertion) for this request, or are you potentially reusing a previous one? If you haven’t, you will want to try generating a new assertion immediately before sending it to the /token endpoint

also, the grant_type value should be just “client_credentials”

1 Like

I just tried with grant_type as client_credentials but still same..And every time am generating new JWT..mean in claims i set issuetime and exp time…below is my code for creating signed JWT

private String createSignedJwt() throws Exception {
    System.out.println(clientId);
    RSAPrivateKey privateKey = loadPrivateKey();
    RSAKey rsaKey = RSAKey.parse(new String(
            new ClassPathResource(privateKeyPath).getInputStream().readAllBytes()
    ));
    System.out.println("kid from JWK = " + rsaKey.getKeyID());
    System.out.println("clientId = " + clientId);
    System.out.println("audience = " + tokenUrl);



    JWSSigner signer = new RSASSASigner(privateKey);

    JWTClaimsSet claims = new JWTClaimsSet.Builder()
            .issuer(clientId)
            .subject(clientId)
            .audience(tokenUrl)
            .issueTime(new Date())
            .expirationTime(new Date(System.currentTimeMillis() + 5 * 60 * 1000))
            .jwtID(UUID.randomUUID().toString())
            .build();

    SignedJWT signedJWT = new SignedJWT(
            new JWSHeader.Builder(JWSAlgorithm.RS256)
                    .keyID("XXXXXXX")
                    .build(),
            claims
    );

    signedJWT.sign(signer);

    return signedJWT.serialize();
}--have redacted KID for security..and updated headers    MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "client_credentials");
        body.add("assertion", assertion);
        body.add("scope", "openid");

and am loading provate key from json file 
 private RSAPrivateKey loadPrivateKey() throws Exception {
        //String jwkJson = Files.readString(Paths.get(privateKeyPath));
        ClassPathResource resource = new ClassPathResource("okta-private-key.json");
        String jwkJson = new String(resource.getInputStream().readAllBytes());
        RSAKey rsaKey = RSAKey.parse(jwkJson);
        return rsaKey.toRSAPrivateKey();
    }

Apparently I had to set client_id in the request body.After that earlier error is gone but now am getting “{“error”:“invalid_dpop_proof”,“error_description”:“The DPoP proof JWT header is missing.”}”…no idea what this means..any inputs please?

Somehow as per googling figured out had to disable DPoP option in okta admin console for the service app.After having done that now am getting invalid_scope {“error”:“invalid_scope”,“error_description”:“One or more scopes are not configured for the authorization server resource.”}"…tried with body.add(“scope”, “okta.users.read”); still same issue..appraently when I check “Admin Roles” tab for the tab there are no admin roles assigned and I believe for free integrator account we cannot assign admin roles to apps..so what are my options now to test this flow?Also for okta.users.read under Okta API scopes grant is enabled

SUCCESSS!!!Had to create a custom scope in service app api.read and grant access for this app okta.read.users cannot be used as scope for default authorization server.Thanks everyone.

2 Likes

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.