Skip to content
This repository has been archived by the owner. It is now read-only.

Authorization Code Grant Flow with PKCE #16

Open
ryanmr opened this issue Feb 1, 2018 · 10 comments
Open

Authorization Code Grant Flow with PKCE #16

ryanmr opened this issue Feb 1, 2018 · 10 comments

Comments

@ryanmr
Copy link

@ryanmr ryanmr commented Feb 1, 2018

Hey there, I'd like to request input or discussion on using Auth0's PKCE flow that allows for a refresh token in addition to the standard id token and access token.

The standard Implicit flow works fine, as demonstrated in the example code. This flow is important in mobile applications, and example here would be extremely valuable and helpful for those using the Expo flavor of React Native and integrating with Auth0. Their documentation makes use of the Node crypto library, which appears to be unavailable in a React Native app.

@BenjaminWatts
Copy link

@BenjaminWatts BenjaminWatts commented Feb 9, 2018

any luck with this - I am not sure how to use the access token provided as it doesn't look like a full JSON token - and the documentation seems to be quite lacking on this front - any ideas?

@ryanmr
Copy link
Author

@ryanmr ryanmr commented Feb 9, 2018

Hi @BenjaminWatts. Your issue may be unrelated to the lack of support of crypto as noted above.

At least with Auth0, my understanding is: to get a JWT access_token back, you need to supply an audience matching your API audience value when you make your initial /authorize request. Without the audience, Auth0 seems to return a generic access token, rather than a JWT access_token.

@zth
Copy link

@zth zth commented Oct 14, 2018

I'm also very interested in this. Has anyone gotten this working in a reasonable way?

@mo
Copy link

@mo mo commented Oct 15, 2018

@zth If you have access to a high quality random number source (which afaik is not available in non-ejected expo), you can do something like this:

    const redirectUrl = AuthSession.getRedirectUrl();
    const verifier = base64URLEncode(randomBytes(32));
    const challenge = createChallenge(verifier);
    const result = await AuthSession.startAsync({
      authUrl:
        `${auth0Domain}/authorize` +
        toQueryString({
          audience: "https://some.hostname.com/some/api/url",
          client_id: auth0ClientId,
          response_type: "code",
          scope: "openid profile email",
          code_challenge: challenge,
          code_challenge_method: "S256",
          redirect_uri: redirectUrl,
          connection: "optional-id-for-connection-that-will-be-preselected-auth0-lock-screen"
        })
    });

    if (result.type !== "success") {
      throw Error(
        `result.type was ${
          result.type
        } instead of "success", full result was: ${JSON.stringify(
          result,
          null,
          2
        )}`
      );
    }
    const code = result.params.code;
    const body = {
      grant_type: "authorization_code",
      client_id: auth0ClientId,
      code_verifier: verifier,
      code,
      redirect_uri: redirectUrl
    };
    const resp = await fetch("https://some.host.domain.com/oauth/token", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    });
    const respJson = await resp.json();
    console.log("respJson=", JSON.stringify(respJson, null, 2));
    const token: IJwtAccessToken = jwtDecoder(respJson.access_token);
    console.log("token=", JSON.stringify(token, null, 2));
    const email = token.sub.split("|")[1];
@ryanmr
Copy link
Author

@ryanmr ryanmr commented Oct 16, 2018

To follow up on this, for what it's worth, we decided to make use of a public endpoint that would return the verifier and challenge.

We have an express endpoint serve the following. It's not great, but works for now. Most of our users only ever log in once.

const verifier = base64.encode(crypto.randomBytes(32))
const challenge = base64.encode(sha256(verifier))
return {
  verifier,
  challenge
}
@zth
Copy link

@zth zth commented Oct 17, 2018

Thank you for the responses! I ended up doing the same thing, exposing an endpoint for the challenge/verifier. I think expo is working on supporting a native crypto module that can get safe random numbers, so I'll migrate to that whenever that's available. But the endpoint is fine for now for me at least. Thanks!

@benburton
Copy link

@benburton benburton commented Jan 22, 2019

Doesn't exposing an endpoint for challenge/verifier defeat the purpose of signing with PKCE?

@debolel
Copy link

@debolel debolel commented May 31, 2019

hi there, is there any update here? this is still an issue and it's impossible to get a refresh token with social login (Google) without using PKCE flow, and we don't have crypto module available until SDK33... :(

@mo
Copy link

@mo mo commented Jun 1, 2019

Expo SDK 33 has been released now and comes with https://www.npmjs.com/package/expo-random

@formaldehydeson
Copy link

@formaldehydeson formaldehydeson commented Jan 27, 2020

came up with this Expo solution based on the JavaScript example in the Auth0 Doc:

import { AuthSession } from 'expo';
import * as Crypto from 'expo-crypto';
import * as Random from 'expo-random';

function URLEncode(str) {
    return str.replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}

async function sha256(buffer) {
    return await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, buffer, { encoding: Crypto.CryptoEncoding.BASE64 });
}

const randomBytes = await Random.getRandomBytesAsync(32);
let verifier = URLEncode(btoa(randomBytes.toString()));
let challenge = URLEncode(await sha256(verifier));

const redirectUrl = AuthSession.getRedirectUrl();
let authUrl = `${auth0Domain}/authorize?` + this.toQueryString({
    audience: `${auth0Audience}`,
    client_id: `${auth0ClientID}`,
    connection: 'facebook',
    scope: 'openid profile email offline_access',
    redirect_uri: redirectUrl,
    response_type: 'code',
    code_challenge: challenge,
    code_challenge_method: "S256"
    nonce: 'test',
});

const result = await AuthSession.startAsync({
    authUrl: authUrl
});

if (result.type === 'success' && result.params && result.params.code)
    let code = result.params.code;
    // Proxy call to /oauth/token through backend API using code, verifier, and redirectUrl
    // /oauth/token will return a 401 if called from JavaScript front end
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
7 participants
You can’t perform that action at this time.