Search code examples
node.jsaccess-tokenauth0alexa-skills-kit

Auth0 userinfo request fails to process


I'm trying to make authenticated calls to GitHub using Auth0 as an intermediary.

I'm following the guide on https://auth0.com/docs/connections/calling-an-external-idp-api, but when it comes to step2. getting user_id, I run into an issue with the /userinfo endpoint.

const rp = require('request-promise')

const auth0_options = {
method: 'POST',
url: 'https://MY_DEV_ID/oauth/token',
headers: {'content-type': 'application/x-www-form-urlencoded'},
form: {
    grant_type: 'client_credentials',
    client_id: 'MY_CLIENT_ID',
    client_secret: 'MY_CLIENT_SECRET',
    audience: 'https://MY_DEV_ID/api/v2/'
    }
};
const auth0_result = JSON.parse(await rp(auth0_options));
const access_token_one = auth0_result.access_token;

Then I try to call the userinfo endpoint as described on https://auth0.com/docs/api/authentication#user-profile

const userinfo_options = { method: 'GET',
url: 'https://MY_DEV_ID/userinfo',
headers: { 'Authorization' : `Bearer ${access_token_one}`} 
};
const userinfo_result = JSON.parse(await rp(userinfo_options));

but this one does not work, it returns an empty object which I assume is due to it being unauthorized.

I have looked up many previous questions about /userinfo here on SO, but have found that in all the cases I found the issue was something I've already secured (token, audience etc)

Edit: openid configuration for the auth0 api I'm trying to use:

scopes_supported": [
    "openid",
    "profile",
    "offline_access",
    "name",
    "given_name",
    "family_name",
    "nickname",
    "email",
    "email_verified",
    "picture",
    "created_at",
    "identities",
    "phone",
    "address"
  ],
  "response_types_supported": [
    "code",
    "token",
    "id_token",
    "code token",
    "code id_token",
    "token id_token",
    "code token id_token"
  ],

Solution

  • Okay so apparently the access token you get in my auth0_result is not the currect one for userinfo, that one requires a different token. I am running this code in an alexa skill, my initial access token comes from an account linking in the setup process.

    Here's the final working solution for anyone who ran into this issue: (my solution is for amazon alexa, but with some changes it should work for whatever site you're trying to connect)

    let attributes = handlerInput.attributesManager.getSessionAttributes();
    attributes.loginToken = handlerInput.requestEnvelope.context.System.user.accessToken;
    login_options = {
        method: 'GET',
        uri: '{YOUR_AUTH0_TENANT_ID}/userinfo',
        headers : {
            Authorization : 'Bearer ' + attributes.loginToken,
        }
    };
    const login_result = JSON.parse(await rp(login_options));
    attributes.loggedInUser = login_result.nickname;
    
    const auth0_options = {
    method: 'POST',
    url: '{YOUR_AUTH0_TENANT_ID}/oauth/token',
    headers: {'content-type': 'application/x-www-form-urlencoded'},
    form: {
        grant_type: 'client_credentials',
        client_id: '{AUTH0_APPLICATION_CLIENT_ID}',
        client_secret: '{AUTH0_APPLICATION_CLIENT_SECRET}',
        audience: '{YOUR_AUTH0_TENANT_ID}/api/v2/',
    }
    };
    const auth0_result = JSON.parse(await rp(auth0_options));
    const access_token_one = auth0_result.access_token;
    
    const github_options = {
        method: 'GET',
        url: `{YOUR_AUTH0_TENANT_ID}/api/v2/users/${login_result.sub}`,
        headers : {'authorization' : 'Bearer ' + access_token_one}
    };
    const github_result = JSON.parse(await rp(github_options));
    attributes.token = github_result.identities[0].access_token;
    handlerInput.attributesManager.setSessionAttributes(attributes);
    

    after this, attributes.token will contain an access token you can use to authenticate yourself on GitHub (set token expiration to max (30 days) on auth0 or store refresh token for later use), example http request:

    function customhttpgetrequest(url, attributes){
        const customHeaderRequest = request.defaults({
            headers: {
                'User-Agent': attributes.loggedInUser,
                'Authorization': 'Bearer ' + attributes.token
            }
        })
        return new Promise((resolve, reject) => {
            customHeaderRequest.get(url, (error, response, body) => {
                if (error) {
                    return reject(error)
                }
                resolve(JSON.parse(body)); /*simply resolve(body) should work aswell, not sure why i parse it here*/
            })
        })
    }