2

I'm using passport-oauth2 (passportjs.org and https://github.com/jaredhanson/passport-oauth2/blob/master/lib/strategy.js) for OAuth2+PKCE integration in a nodejs application.

The backend it's authenticating against is written in Java.

The problem is that I can't seem to decode->hash the code_verifier to correctly match the code_challenge that comes from passport-oauth2.

I know that the Base64 encoding that comes from passport has been generated to be URL safe (no padding, no wrapping, replacements for + or /), so I'm using a Url Decoder:

Base64.getUrlDecoder().decode(...)

Then I'm using commons DigestUtils to generate a SHA256 of the decoded verifier and comparing it with the challenge. So the whole thing looks something like this:

    java.util.Base64.Decoder decoder = java.util.Base64.getUrlDecoder();
    String codeChallenge = // get the code challenge from my cache
    byte[] decodedCodeChallenge = decoder.decode(codeChallenge);
    byte[] decodedCodeVerifier = decoder.decode(codeVerifier);
    if (!Arrays.equals(sha256(decodedCodeVerifier), decodedCodeChallenge)) {
        return Response.status(400).entity(ERROR_INVALID_CHALLENGE_VERIFIER).build();
    }

Example:

This code verifier: 5CFCAiZC0g0OA-jmBmmjTBZiyPCQsnq_2q5k9fD-aAY should match this code challenge: Fw7s3XHRVb2m1nT7s646UrYiYLMJ54as0ZIU_injyqw once both have been Base64-url-decoded and the verifier has been SHA256 hashed, but it doesn't.

What am I doing wrong?

ndtreviv
  • 3,473
  • 1
  • 31
  • 45

1 Answers1

1

Just 5 minutes later I figured it out.

In passport-oauth2, the code verifier is Base64-url-encoded(random bytes):

verifier = base64url(crypto.pseudoRandomBytes(32))

See: https://github.com/jaredhanson/passport-oauth2/blob/master/lib/strategy.js#L236

The challenge is then Base64-url-encoded(sha256(verifier)), which expands to Base64-url-encoded(sha256(Base64-url-encoded(random bytes))):

challenge = base64url(crypto.createHash('sha256').update(verifier).digest());

See: https://github.com/jaredhanson/passport-oauth2/blob/master/lib/strategy.js#L242

So to do the verification, I don't need to decode anything. It was sha256-d in it's encoded state.

This worked in the end:

    java.util.Base64.Encoder encoder = java.util.Base64.getUrlEncoder();
    String codeChallenge = // get code challenge from my cache;
    String encodedVerifier = new String(encoder.encode(sha256(codeVerifier))).split("=")[0]; // Remember to remove padding
    if (!encodedVerifier.equals(codeChallenge)) {
        return Response.status(400).entity(ERROR_INVALID_CHALLENGE_VERIFIER).build();
    }
ndtreviv
  • 3,473
  • 1
  • 31
  • 45
  • what is crypto here ? crypto.pseudoRandomBytes(32) ? Im trying to generate a code verifier and a code_challenge but I don't know what is crypto ? – Afsanefda May 26 '20 at 04:24
  • 1
    @Afsanefda It's https://nodejs.org/api/crypto.html Remember: the client in this scenario was a NodeJS client running passportjs. The server was Java. – ndtreviv May 27 '20 at 14:51