Tom Oakley Tom Oakley - 18 days ago 7
Javascript Question

Get Auth0 access_token from existing id_token

I'm using auth0 to authenticate my logins to my Single Page App (built on React). I'm mostly using the base API calls (listed here).

The process I'm using is:

get username/email and password when the user enters them on my app's login page
Send a POST request to

/oauth/ro
with those values - here is that code:

export const login = (params, err) => {
if (err) return err
const {email, password} = params
const {AUTH0_CLIENT_ID, AUTH0_DOMAIN} = process.env
return fetch(`${AUTH0_DOMAIN}/oauth/ro`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
'client_id': AUTH0_CLIENT_ID,
'username': email,
'password': password,
'connection': 'Username-Password-Authentication',
'grant_type': 'password',
'scope': 'openid',
'device': '',
'id_token': ''
})
})
.then(response => response.json())
.then(json => {
const {id_token, access_token} = json
setCookieValue('id_token', id_token) // utility function I wrote
return getProfile(access_token)
.then(data => {
const {user_id, email: emailAddress, picture, name} = data
return {id_token, user_id, emailAddress, picture, name}
})
})
.catch(error => console.log(`ERROR: ${error}`))
}


This is all sent through Redux and the user is logged in (assuming the username/password was correct).

However, I'm trying to figure out how to persist the login when refreshing the page/coming back to the app. I'm saving the
id_token
(which is a JWT) in the browser's cookies and can fetch this when the app renders server-side. I can decode the JWT and get the payload (
sub
is the user ID from auth0). However, to get the profile data I need the
access_token
which Auth0 provides when using the
/oauth/ro
POST request. Obviously, if the JWT token has expired then it will just reject it and keep the user logged out.

Here is my code to decode the JWT (happens on app render):

const ID_TOKEN = req.cookies.id_token || false
if (ID_TOKEN) {
verifyJwt(ID_TOKEN, (err, decoded) => {
if (err) { console.log(`JWT Verification error: ${err}`) }
else {
const {sub} = decoded
getProfile(sub).then(data => store.dispatch(fetchUserDetails(data))) // fails as `sub` (the user id) is not the `access_token` which it requires
}
})
}


I have tried using the
/oauth/ro
call again, but this time specifying
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer"
and using the
id_token
retrieved from the cookies, and specifying a
device
. However, when I do this call, I get this error from Auth0:

{
"error": "invalid_request",
"error_description": "there is not an associated public key for specified client_id/user_id/device"
}


So my question is, what API call do I need to make to get the
access_token
from the
id_token
JWT?

Also, as a bonus - when I do the POST request to login, the
password
is being transfered over plaintext. How would I encrypt this when sending to auth0 so they can decrypt it back? I assume it involves using the
client_secret
which auth0 provide but I'm not sure how to go about doing that.

Answer

The ability to refresh a token programmatically without any type of user interaction is accomplished through the use of refresh tokens. However, this is not applicable for browser-based applications because refresh tokens are long-lived credentials and the storage characteristics for browsers would place them at a too bigger risk of being leaked.

If you want to continue to use the resource owner password credentials grant you can choose to ask the user to input the credentials again when the tokens expire. As an alternative, upon authentication you can obtain the required user information and initiate an application specific session. This could be achieved by having your server-side logic create an application specific session identifier or JWT.

You can also stop using the resource owner password credentials grant and redirect the user to an Auth0 authentication page that besides returning the tokens to your application would also maintain an authenticated session for the user, meaning that when the tokens expired and your application redirected again to Auth0, the user might not need to manual reenter credentials because the Auth0 session is still valid.

In relation to the password being sent in plaintext; the resource owner endpoint relies on HTTPS so the data is encrypted at the protocol level. You must also use HTTPS within your own application for any type of communication that includes user credentials of any kind.

Also note that you can control what's returned within the ID token through the use of scopes, depending on the amount of information in question you might not even need to make additional calls to get the user profiles if you signal that you want that information to be contained within the ID token itself.