Not able to get data using access token generate by client credentials using DPoP

I am using a service Application in Okta. I have used public-key / private key as client authentication and enabled DPop (Require Demonstrating Proof of Possession (DPoP) header in token requests).

I have generated an RSA key converted it into JWK and uploaded it to my service Application in Okta.

I have generated a DPoP proof and client assertion. Then I hit access token API But got an error
From the error header, I got the Dpop nonce, Again I generated DPoP proof and Hit access token API. Now I have the access token.

Now I want to hit any API like Get groups But I got this error: Request failed with status code 400
I have decoded the access token it has all the necessary information.

Can someone tell me How to get an access token in an DPoP enabled service Application by which I can hit any API and get the data?

I am providing the code:

const crypto = require(‘crypto’);
const sshpk = require(‘sshpk’);
const jwt = require(‘jsonwebtoken’);
const axios = require(‘axios’);
const { v4: uuidv4 } = require(‘uuid’);
const fs = require(‘fs’);
const forge = require(‘node-forge’);
const username = ‘vikalp.khandelwal@robomq.io’
const tokenURL = ‘https://dev-97298997.okta.com/oauth2/v1/token
const groupsURL = ‘https://dev-97298997.okta.com/api/v1/groups

// Step 1: Generate RSA key pair
const generateRsaKey = async (username) => {
const { privateKey, publicKey } = crypto.generateKeyPairSync(‘rsa’, {
modulusLength: 2048,
publicKeyEncoding: {
type: ‘pkcs1’,
format: ‘pem’,
},
privateKeyEncoding: {
type: ‘pkcs1’,
format: ‘pem’,
},
});

// SSH Key Manipulation
const publickey = sshpk.parseKey(publicKey, 'pem');
const pubKey = publickey.toString('ssh');
let usernameRSA = username.replace(/[&\/\\#,+()$~%.'":*?<>{}]/g, '_');
usernameRSA = usernameRSA.substr(0, usernameRSA.lastIndexOf('_')); 
usernameRSA = checkUsername(usernameRSA);
let sshRsa = pubKey.replace('(unnamed)', usernameRSA);
let privatekey = privateKey.replace(/\n/g, " ");

const keyPairs = {
    user_publickey: sshRsa,
    user_privatekey: privatekey,
    user_certificate: generateCertificate(sshRsa, privateKey, username),
};

fs.writeFileSync('pubkey.txt', keyPairs.user_publickey, function (err) {
    if (err) throw err;
});
fs.writeFileSync('pvtKey.txt', keyPairs.user_privatekey, function (err) {
    if (err) throw err;
});
fs.writeFileSync('certificate.txt', keyPairs.user_certificate, function (err) {
    if (err) throw err;
});

}

function checkUsername(usernameRSA) {
if (usernameRSA.length > 32) {
const reglastintial = (/[A-Za-z0-9_]+_[A-Za-z0-9]/)
const domainname = usernameRSA.substr(usernameRSA.lastIndexOf(‘@’), usernameRSA.length - 1)
usernameRSA = usernameRSA.substr(0, usernameRSA.lastIndexOf(‘@’))
let intialusername = usernameRSA

  usernameRSA = ((reglastintial).test(usernameRSA) ? usernameRSA.match(reglastintial)[0] : usernameRSA)

  let last_intital = null
  if ((usernameRSA + domainname).length > 32) {
    if ((/_[A-Za-z0-9]+$/).test(intialusername)) {
      last_intital = intialusername.match(/_[A-Za-z0-9]+$/)[0]
    }
    usernameRSA = ((/[A-Za-z0-9]/).test(intialusername.charAt(0)) ? intialusername.match(/[A-Za-z0-9]/)[0] : intialusername.match(/_[A-Za-z0-9]/)[0])
    if (last_intital != null) {
      usernameRSA += last_intital
    }
  }

  if ((usernameRSA + domainname).length > 32) {
    usernameRSA = ((reglastintial).test(usernameRSA) ? usernameRSA.match(reglastintial)[0] : usernameRSA)
  }
  usernameRSA = usernameRSA + domainname
  usernameRSA = usernameRSA.replace(/[&\/\\#,+()$~%.'":*?<>{}@]/g, '_')
}
else {
  usernameRSA = usernameRSA.replace(/[&\/\\#,+()$~%.'":*?<>{}@]/g, '_')
}
return usernameRSA

}

const getAsymmetricPrivateKey = async (privateKey) => {
try {
const regex = /-----BEGIN RSA PRIVATE KEY-----(.*?)-----END RSA PRIVATE KEY-----/s;
let privatePEMKey = privateKey.replace(regex, (_, content) => {
const replacedContent = content.split(’ ‘).join(’\n’);
return -----BEGIN RSA PRIVATE KEY-----${replacedContent}-----END RSA PRIVATE KEY-----;
});
return crypto.createPrivateKey({ key: privatePEMKey, format: ‘pem’ });
} catch (error) {
throw error;
}
};

const generateCertificate = (publicKey, privateKey, username) => {
const key = sshpk.parseKey(publicKey, ‘ssh’);
publicKey = key.toString(‘pem’);

let keys = {
    publicKey: forge.pki.publicKeyFromPem(publicKey),
    privateKey: forge.pki.privateKeyFromPem(privateKey),
};
  
let cert = forge.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = '01';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 20);

let attrs = [{
    name: 'commonName',
    value: username,
}];

cert.setSubject(attrs);
cert.setIssuer(attrs);
cert.sign(keys.privateKey);
return forge.pki.certificateToPem(cert);

};

const getFingerprint = async (cert) => {
try {
const certificate = forge.pki.certificateFromPem(cert);
const md = forge.md.sha1.create();
md.update(forge.asn1.toDer(forge.pki.certificateToAsn1(certificate)).getBytes());
const thumbprint = md.digest().toHex();
const binaryFingerprint = Buffer.from(thumbprint, ‘hex’);
return binaryFingerprint.toString(‘base64’);
} catch (error) {
console.error(error);
throw error;
}
};

// Step 3: Convert Public Key to JWK format
const convertJWK = async (pubKey, certFingerprint) => {
const sshKey = sshpk.parseKey(pubKey, ‘ssh’);
if (sshKey.parts.length >= 2) {
const exponent = sshKey.parts[0].data.toString(‘base64’);
const modulus = sshKey.parts[1].data.toString(‘base64’);
const jwk = {
kty: ‘RSA’,
e: exponent,
n: modulus,
alg: ‘RS256’,
use: ‘sig’,
kid: certFingerprint
};

    console.log(JSON.stringify(jwk, null, 2));
    fs.writeFileSync('jwk.txt', JSON.stringify(jwk, null, 2), function (err) {
        if (err) throw err;
    });
}

};

// Step 4 & 7: Regenerate DPoP proof with nonce and retry
const generateDpop = async (jwk, pvtKey, url, method, nonce) => {
const header = {
alg: ‘RS256’,
typ: ‘dpop+jwt’,
jwk: JSON.parse(jwk) // Ensure this is parsed correctly
};
const proofPayload = {
jti: uuidv4(),
htu: url, // URI of the resource (token endpoint or API endpoint)
htm: method, // HTTP method
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 300, // Expire in 5 minutes
};

// Include nonce if present
if (nonce) proofPayload.nonce = nonce;
else proofPayload.nonce = uuidv4()


const dpopProof = jwt.sign(proofPayload, pvtKey, {
    algorithm: 'RS256',
    header: header,
});
// Log for debugging
console.log('Generated DPoP Proof:', dpopProof)
return dpopProof;

};

// Step 5: Get Client assertion
const generateClientAssertion = async (pKey, certFingerprint) => {
const payload = {
iss: ‘0oajycc0vu4uIxLvT5d7’,
sub: ‘0oajycc0vu4uIxLvT5d7’,
aud: tokenURL,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (60 * 60),
};

// JWT Header
const header = {
    alg: 'RS256',
    typ: 'JWT',
    kid: certFingerprint
}

// Signing client assertion JWT
return jwt.sign(payload, pKey, { algorithm: 'RS256', header })

}

// Step 6 & 8: Get Access Token
const getAccessToken = async (clientAssertion, dpopProof) => {
try {
const response = await axios({
method: ‘POST’,
url: tokenURL,
headers: {
‘Accept’: ‘application/json’,
‘Content-Type’: ‘application/x-www-form-urlencoded’,
‘DPoP’: dpopProof // Include DPoP proof in headers
},
data: new URLSearchParams({
‘grant_type’: ‘client_credentials’,
‘client_id’: ‘0oajycc0vu4uIxLvT5d7’,
‘client_assertion_type’: ‘urn:ietf:params:oauth:client-assertion-type:jwt-bearer’,
‘client_assertion’: clientAssertion,
‘scope’: ‘okta.users.manage okta.groups.manage okta.schemas.read okta.groups.read’ ,
}).toString(),
});
console.log(‘access_token>>’, response.data.access_token)
return response.data.access_token;
} catch (error) {
if (error.response && error.response.headers[‘dpop-nonce’]) {
return error.response.headers[‘dpop-nonce’]; // Return nonce for retry
} else {
throw new Error(Error fetching access token: ${error.message});
}
}
};

// Step 9: Convert base64 hash
const convertHash = (access_token) => {
const boundAccessToken = access_token
const hash = crypto.createHash(‘sha256’).update(boundAccessToken).digest(‘base64’);
return hash;
}

// Step 10: Use DPoP-bound token to access groups
const generateModifiedDpop = async (jwk, pvtKey, url, method, hash) => {
var claims = {
htm: method,
htu: url,
ath: hash
}
dpopJWKForResource = jwt.sign(claims, pvtKey,
{
algorithm: ‘RS256’,
header:
{
typ: ‘dpop+jwt’,
jwk: jwk
}
}
);
console.log(‘dpopJWKForResource>>>’, dpopJWKForResource);
return dpopJWKForResource;
}

// Step 11: Get Groups
const getGroups = async (dpopProof, accessToken) => {
try {
const response = await axios.get(groupsURL, {
headers: {
Authorization: DPoP ${accessToken},
DPoP: dpopProof
}
});
console.log(response.data);
} catch (error) {
console.error(‘Error fetching groups:’, error.message);
}
};

// Final Function to execute the flow
const executeFlow = async (username) => {
try {
// Step 1: Generate RSA key pair
await generateRsaKey(username);

    // Step 2: Load keys from files
    const pubKey = fs.readFileSync('pubkey.txt', 'utf8');
    const pvtKey = fs.readFileSync('pvtKey.txt', 'utf8');
    const pKey = await getAsymmetricPrivateKey(pvtKey);

    const certificate = fs.readFileSync('certificate.txt', 'utf8');  // Assume you already generated this certificate
    const certFingerprint = await getFingerprint(certificate);

    // Step 3: Convert public key to JWK and calculate certificate fingerprint
    await convertJWK(pubKey, certFingerprint);
    const jwk = fs.readFileSync('jwk.txt', 'utf8')

    // Step 4: Generate initial DPoP proof
    let dpopProof = await generateDpop(jwk, pKey, tokenURL, 'POST');
    let access_token

    // Step 5: Get Client assertion
    const clientAssertion  = await generateClientAssertion(pKey, certFingerprint)

    // Step 6: Get nonce using access Token API
    let nonce = await getAccessToken(clientAssertion, dpopProof)  // Handle nonce case
    if (nonce) {
        // Step 7: Regenerate DPoP proof with nonce and retry
        dpopProof = await generateDpop(jwk, pKey, tokenURL, 'POST', nonce);
        // Step 8: Get access Token
        access_token = await getAccessToken(clientAssertion, dpopProof);
        // Step 9: Convert base64 hash
        const hash = convertHash(access_token)
        // Step 10: Use DPoP-bound token to access groups
        const dpopJWKForResource = await generateModifiedDpop(jwk, pKey, groupsURL, 'GET', hash);
        // Step 11: Get Groups
        await getGroups(dpopJWKForResource, access_token);
    }
} catch (error) {
    console.error('Error executing flow:', error.message);
}

};

// Trigger the flow
executeFlow(username); Not able to get data using access token generate by client credentials using DPoP