I am building a cordova mobile app and trying to use the auth0 lock API. I am having trouble with the refresh token. I can retreive the refresh token in the authResult but cannot figure out how to actually refresh the id_token ( I suppose i could write the REST calsl myself )
In the v9 docs, it seems there used to be a method: https://auth0.com/docs/libraries/lock/v9/using-a-refresh-token
lock.getClient().refreshToken(refresh_token, function (err, delegationResult) {
// Get here the new JWT via delegationResult.id_token
});
However in lock v10 it seems this method doesn't exist any more: https://auth0.com/docs/libraries/lock/v10/api
Can anyone advise if there is a way to refresh the token using the lock API?
First, you need to either have included Auth0's script tag in your HTML:
<script src="https://cdn.auth0.com/js/lock/10.8/lock.min.js"></script>
Or, if you've installed from npm, you can require Auth0:
var Auth0 = require("auth0-js");
In V10, you create an instance of the Auth0 client (separate from the Auth0Lock instance) which has a function refreshToken():
var auth0 = new Auth0({clientID: YOUR_CLIENT_ID, domain: YOUR_AUTH0_DOMAIN});
...
auth0.refreshToken(refresh_token_here, (err, resp) => {
// resp: {expires_in: 36000, id_token: "id_token here", token_type: "Bearer"}
}
The same can also be achieved by using the getDelegationToken() function:
auth0.getDelegationToken({
client_id: YOUR_CLIENT_ID,
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
refresh_token: refresh_token_here,
scope: "openid",
api_type: "auth0"
}, (err, resp) => {
// resp: {expires_in: 36000, id_token: "id_token here", token_type: "Bearer"}
});
Related
I have implemented the new Google Identity Services to get an access_token to call the Youtube API.
I try to use this on an Angular app.
this.tokenClient = google.accounts.oauth2.initTokenClient({
client_id: googleApiClientId,
scope: 'https://www.googleapis.com/auth/youtube.readonly',
callback: (tokenResponse) => {
this.accessToken = tokenResponse.access_token;
},
});
When I call this.tokenClient.requestAccessToken(), I can get an access token and use the Youtube API, that works.
But after one hour, this token expires. I have this error : "Request had invalid authentication credentials."
How can I get the newly refreshed access_token transparently for the user ?
There are two authorization flows for the Google Identity Services (GIS) library:
The implicit flow, which is client-side only and uses .requestAccessToken()
The authorization code flow, which requires a backend (server-side) as well and uses .requestCode()
With the implicit flow (which is what you are using), there are no refresh tokens. It is up to the client to detect tokens aging out and to re-run the token request flow. Here is some sample code from google's examples for how to handle this:
// initialize the client
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
prompt: 'consent',
callback: '', // defined at request time in await/promise scope.
});
// handler for when token expires
async function getToken(err) {
if (err.result.error.code == 401 || (err.result.error.code == 403) &&
(err.result.error.status == "PERMISSION_DENIED")) {
// The access token is missing, invalid, or expired, prompt for user consent to obtain one.
await new Promise((resolve, reject) => {
try {
// Settle this promise in the response callback for requestAccessToken()
tokenClient.callback = (resp) => {
if (resp.error !== undefined) {
reject(resp);
}
// GIS has automatically updated gapi.client with the newly issued access token.
console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));
resolve(resp);
};
tokenClient.requestAccessToken();
} catch (err) {
console.log(err)
}
});
} else {
// Errors unrelated to authorization: server errors, exceeding quota, bad requests, and so on.
throw new Error(err);
}
}
// make the request
function showEvents() {
// Try to fetch a list of Calendar events. If a valid access token is needed,
// prompt to obtain one and then retry the original request.
gapi.client.calendar.events.list({ 'calendarId': 'primary' })
.then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
.catch(err => getToken(err)) // for authorization errors obtain an access token
.then(retry => gapi.client.calendar.events.list({ 'calendarId': 'primary' }))
.then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
.catch(err => console.log(err)); // cancelled by user, timeout, etc.
}
Unfortunately GIS doesn't handle any of the token refreshing for you the way that GAPI did, so you will probably want to wrap your access in some common retry logic.
The important bits are that the status code will be a 401 or 403 and the status will be PERMISSION_DENIED.
You can see the details of this example here, toggle to the async/await tab to see the full code.
To refresh the access token in a transparent way for the end-user you have to use the Refresh Token, This token will also come in the response to your call.
With this token, you can do a POST call to the URL: https://www.googleapis.com/oauth2/v4/token with the following request body
client_id: <YOUR_CLIENT_ID>
client_secret: <YOUR_CLIENT_SECRET>
refresh_token: <REFRESH_TOKEN_FOR_THE_USER>
grant_type: refresh_token
refresh token never expires so you can use it any number of times. The response will be a JSON like this:
{
"access_token": "your refreshed access token",
"expires_in": 3599,
"scope": "Set of scope which you have given",
"token_type": "Bearer"
}
#victor-navarro's answer is correct, but I think the URL is wrong.
I made a POST call to https://oauth2.googleapis.com/token with a body like this and it worked for me:
client_id: <YOUR_CLIENT_ID>
client_secret: <YOUR_CLIENT_SECRET>
refresh_token: <REFRESH_TOKEN_FOR_THE_USER>
grant_type: refresh_token
I'm trying to setup discord oauth2 pkce using passportjs and the passport-oauth2
const discordStrategy = new OAuth2Strategy({
authorizationURL: 'https://discord.com/api/oauth2/authorize',
tokenURL: 'https://discord.com/api/oauth2/token',
clientID: DISCORD_CLIENT_ID,
clientSecret: DISCORD_CLIENT_SECRET,
callbackURL: DISCORD_CALLBACK_URL,
state: true,
pkce: true,
scope: ['identity', 'scope'],
passReqToCallback: true,
},
(req: Request, accessToken: string, refreshToken: string, profile: DiscordUserProfile, cb: any) => {
prisma.user.findUnique({ where: { email: profile.email ?? '' }}).then(foundUser => {
if (foundUser === null) {
// Create a new user with oauth identity.
} else {
cb(null, foundUser)
}
}).catch(error => {
cb(error, null);
})
});
I've been following the google example as well as some others, these examples indicate that, I should be able to use:
passport.use('discord', discordStrategy);
and
authRouter.get('/discord', passport.authenticate('discord'));
and this should redirect to the OAuth2 providers login page, but instead, I get a 400 Bad Request "The request cannot be fulfilled due to bad syntax." The response body contains an object:
{"scope": ["0"]}
Why is this happening instead of the expected redirect?
My intention is that, once the user logs in, I should get a code, then I can post that code and the code verifier to get an access token, then once the access token is obtained, the actual authenticate call can be made
Edit: I put breakpoints in the passport.authenticate function and I stepped through it. It does actually get through everything and it calls the redirect. The parsed URL it generates, even if I copy it and manually navigate to the URL, it gives me the same, just gives:
{"scope": ["0"]}
and no login page, why?
If you add a version number to the base api url, e.g. /v9 it gives a full error message.
It turned out I had typo'd the scopes, I had 'identity' instead of 'identify' - now this part of the process is working as expected.
In my React Native app -- init app not Expo -- I'm trying to refresh the access_token but my POST call is failing with 401. I'm testing this functionality so I make the POST call some 30 seconds after I login so not sure if this plays a role or not.
In my initial login, I do get a refresh_token along with a valid access_token. I then tell my app to wait 30 seconds and make a POST call that looks like this:
const url = 'https://mydomain.auth0.com/oauth/token';
const postOptions = {
method: 'POST',
url: url,
headers: {
"content-type": 'application/x-www-form-urlencoded'
},
form: {
grant_type: 'refresh_token',
client_id: 'MY_CLIENT_ID',
refresh_token: 'REFRESH_TOKEN_RECEIVED_DURING_LOG_IN'
}
};
fetch(url, postOptions)
.then((response) => {
debugger;
// this is where I get response.status 401
})
Any idea what the issue is here?
Also want to mention that under my application settings, Refresh Token is checked under "Grant Types" but refresh token rotation or expiration are NOT enabled.
I figured this out and sharing it in case others need it in the future.
First, Auth0 documentation is misleading at best. They keep mentioning a regular POST call which doesn't work.
In my React Native app, I use their react-native-auth0 library. This library does offer a refreshToken() method which is what I ended up using.
Before I share the code, here are a couple of really important points:
Be sure to include offline_access in the scope of your initial authentication call for the user. Without including offline_access in your scope, you won't get a refresh_token. Once you receive it along with your access_token and id_token, store it as you'll use it many times. This brings me to the second point.
Unless you set it otherwise, your refresh_token doesn't expire. Therefore, store it some place secure and NOT just in AsyncStorage. As mentioned above, unless, you set it otherwise or it gets revoked, your refresh_token doesn't expire and you use it again and again.
With that said, here's the code. Please keep in mind that at start up, I initialize auth0 as a global variable so that I can access it in different parts of my app.
Here's what my initialization looks like in index.js:
import Auth0 from 'react-native-auth0';
global.auth0 = new Auth0({
domain: "MY_DOMAIN.auth0.com",
clientId: "MY_CLIENT_ID",
});
And here's how I use the refreshToken() method:
// First, retrieve the refresh_token you stored somewhere secure after your initial authentication call for the user
global.auth0.auth.refreshToken({ refreshToken: 'MY_REFRESH_TOKEN' })
.then(result => {
// If you're doing it right, the result will include a new access_token
})
you probably need to add the authorization header with your access_token:
const url = 'https://mydomain.auth0.com/oauth/token';
const postOptions = {
method: 'POST',
url: url,
headers: {
"content-type": 'application/x-www-form-urlencoded',
"Authorization" 'bearer '+access_token,
},
body: JSON.stringify({
grant_type: 'refresh_token',
client_id: 'MY_CLIENT_ID',
refresh_token: 'REFRESH_TOKEN_RECEIVED_DURING_LOG_IN'
});
};
fetch(url, postOptions)
.then((response) => {
debugger;
// this is where I get response.status 401
})
I'm trying to migrate my app from auth0 to auth0-spa.js. I've been able to get it almost working, and the code is definitely simpler with this new lib so I'd like to keep using it but I also need a valid jwt token for the backend.
I use the following middleware on my node server (express-jwt)
export const jwtCheckMiddleware = jwt({
secret: '....',
getToken: function (req) {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1];
}
},
issuer: `https://${environment.auth.auth0Domain}/`,
algorithm: 'RS256'
});
Previous I would pass the idToken from auth0 and it worked. Now I get a token via await auth0.getTokenSilently(), however passing that to the middleware gives me "jwt malformed".
How can I get a valid JWT token from auth0-spa.js? Also, how would I ensure that the token I'm passing to the middlware is never expired?
Ok so I was able to get auth-spa.js to give me a jwt token by registering a custom API with Auth0 and then passing the API identifier as the audience.
More info here: https://auth0.com/docs/getting-started/set-up-api
const auth0 = createAuth0Client({
audience: environment.auth.audience, <-----API identifier of custom API in Auth0
client_id: environment.auth.clientId,
domain: environment.auth.clientDomain
});
After adding the audience getTokenSilently() gives me a JWT token which I pass to node. Then I had to add the audience in my jwt middleware:
export const jwtCheckMiddleware = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${environment.auth.auth0Domain}/.well-known/jwks.json`
}),
audience: environment.auth.auth0ApiAudience, <!--- API identifier
issuer: `https://${environment.auth.auth0Domain}/`,
algorithms: ['RS256']
});
I created an app with Vue (including Vue-Router), Node and Express.
I'm trying to secure my App with SSO, using auth0-js.
I created an Auth0-Account and followed this tutorial.
My Login function looks as follows:
import auth0 from 'auth0-js'
var auth = new auth0.WebAuth({
clientID: <my-client-id>,
domain: '<my-auth0-domain>/'
})
export function ssoLogin () {
auth.authorize({
responseType: 'token id_token'
redirectUri: http://localhost:8000/callback,
audience: 'https://<my-auth0-domain>/userinfo',
scope: 'full_access'
})
}
While the login itself works fine, I can't seem to find out how to secure the secret routes of my app.
Following the above mentioned tutorial, I used express-jwt and jwks-rsa, like this:
var jwt = require('express-jwt')
var jwks = require('jwks-rsa')
var authCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: "https://<my-auth0-domain>/.well-known/jwks.json"
}),
audience: <my-client-id>,
issuer: "https://<my-auth0-domain>/",
algorithms: ['RS256']
})
app.post('/send-sensitive-data', authCheck, function (req, res) {
// this post requests sends some data, and should only do so, if a user is logged in
})
However, even if I'm logged in via SSO, when I try to access the sensitive data, I obtain
UnauthorizedError: No authorization token was found
I have no idea where I went wrong. This seems like a very stupid question, but: Can somebody tell me, where the authorization token must be, so it will be found?
I would really appreciate if someone helped me by this or gave me a hint.
Please feel free to ask for more code snippets, if that might help.
Okay, I found the answer on my own ... but in case anyone is making the same mistake as I'm doing: I forgot to set the header in the axios.post request, like that:
axios({
method: 'post',
url: '/send-sensitive-data',
data: somedata,
headers: {
Authorization: 'Bearer ' + getAccessToken()
}
})
.then(response => {
// do something with the response
})