How to get ID token refreshed when using Firebase Auth javascript SDK - javascript

I use a backend app server to communicate with firestore, so the ReactJS client sends ID token to server which in turn communicates with firebase using that token. Client does not make any firebase calls directly other than auth calls. Problem I am facing is, the ID token never gets refreshed and after an hour, backend server fails to recognize token and ends up sending 401 to client. My understanding is that the client should always refresh the token and always send a valid token to backend app server.
firebase.auth().onAuthStateChanged(auth, (user) => {
if (user) {
localStorage.setItem("auth.uid", user.uid)
localStorage.setItem("auth.email", user.email)
user.getIdToken(true).then(function(idToken) {
localStorage.setItem("auth.token", idToken)
})
} else {
// User is signed out
localStorage.removeItem("auth.email")
localStorage.removeItem("auth.uid")
localStorage.removeItem("auth.token")
}
})
firebase.auth().onIdTokenChanged((user) => {
// ** NOT GETTING THIS CALLBACK AFTER 1 HR TO REFRESH **
if (user) {
// User is signed in or token was refreshed
user.getIdToken(true).then(function(idToken) {
localStorage.setItem("auth.token", idToken)
})
}
})
Other option, may be, is to use the REST API to manually fetch a new ID token and use that but having passed forceRefresh value true I would expect this is handled automatically.
What am I missing?

Related

How to configure msalv2 js library to renew token on ExpiresOn instead of extExpiresOn?

I'm trying to use msalv2 js library to do SSO authentication. It mostly works but I find an issue with token expiring and how to renew it.
I am calling the aquireTokenRedirect function to get the new token and auto renew it but I see that it keeps returning the same token. I try to auto renew it when my server side code detects that the token is expired. It uses a msal jwt library to check.
My guess as to what is happening is, my app is sending the id token to the server (I use that instead of access token), and the server decodes it and sees the exp value, which is the ExpiresOn value from the msal js response.
When this time has expired, it will return a 401 error which is fine. However then I try to renew it on the client side, but find it gives me back the same token value...
I check the msal response from earlier and notice there is a extExpiresOn value which is a little later in time. So now i'm thinking the msal library only renews on this time.
So the question is, how can I configure msalv2 js library to renew on the ExpiresOn instead of the extExpiresOn?
Thanks
function getTokenRedirect(request) {
/**
* See here for more info on account retrieval:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
*/
request.account = myMSALObj.getAccountByUsername(username);
return myMSALObj.acquireTokenSilent(request)
.catch(error => {
console.warn("silent token acquisition fails. acquiring token using redirect");
if (error instanceof msal.InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return myMSALObj.acquireTokenRedirect(request);
} else {
console.warn(error);
}
});
}

Firebase ID token has expired. Get a fresh ID token from your client app and try again (auth/id-token-expired)?

I am using postman to mimic requests to firebase and firestore, i am using FBAuth middleware for protected routes like uploading images, posting a comment, so i need to make sure that the user is authenticated before posting an image or writing a comment, but i always get a message in postman that my id token has expired,
FBAuth middleware:
const FBAuth = (req, res, next) => {
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
idToken = req.headers.authorization.split('Bearer ')[1];
} else {
console.error('no token found');
return res.status(400).json({ error: 'unauthorized' })
}
admin.auth().verifyIdToken(idToken).then(decodedToken => {
req.user = decodedToken;
console.log(decodedToken);
return db.collection('users').where('userId', '==', req.user.uid)
.limit(1)
.get();
}).then(data => {
req.user.handle = data.docs[0].data().handle;
return next();
})
.catch(err => {
console.error(err);
return res.status(403).json(err)
})
}
Then use the middleware like this:
app.post('/user/image', FBAuth, uploadImage);`
In post man, i am using the token i get from sign in process to make the request, but i always get that message:
{
"code": "auth/id-token-expired",
"message": "Firebase ID token has expired. Get a fresh ID token from your client app and try again (auth/id-token-expired). See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token."
}
The error message is telling you that you have a problem on the frontend, not on the backend. Your frontend has simply delivered an expired token. It will need to keep refreshing the token every hour, since that's how long they last.
You haven't really said anything about your frontend at all, but it should be using an ID token listener to get fresh tokens delivered every hour. If the client is JavaScript, it would use onTokenIdChanged. If you're copying the token from your web or mobile client for use in postman, know that you will need to keep generating and copying a new tokens during development in order to stay fresh every hour.

Token Authentication - JWT

When we use jsonwebtoken in Node, we sign a particular token for the user and send it back. However, when we verify the token when the user sends it in the header (Authentication: <token>), how does the jwt know that that token which it is verifying is for that particular user and not for some other user who also sent a request at the same time? Does it store the token somewhere internally?
At the time of login, you sign a token where payload is the userId, which is nothing but the _id field in the queried user object.
loginUser: async (req, res, next) => {
try {
const { email, password } = req.body
const user = await User.findOne({ email })
const token = auth.signToken({ userId: user._id })
res.status(200).json({ user, token })
} catch (error) {
return next(error)
}
}
auth.js
function signToken(payload) {
return jwt.sign(payload, JWTSECRET)
}
function verifyToken(req, res, next) {
const token = req.headers.Authorization || req.headers.authorization || ""
if (!token) {
return res.status(403).json({ error: "Not authorized" })
}
jwt.verify(token,JWTSECRET, (err, decodedObj) => {
if (err) {
return res.status(403).json({ error: "Not authorized" })
}
req.user = decodedObj
next()
})
}
module.exports = { signToken, verifyToken }
In the callback of jwt.verify, you get a decodedObj which is like:
{ userId: '5edb3ae6d6b129183c1393bc', iat: 1591425786 }
where iat is the time at which the jwt was issued.
req.user = decodedObj
Here, we're "attaching" the decoded data to the user object so that when a request is made to the protected route, we can get the userId from the request object, like req.user.userId, and then query it from the database.
When you sign a token, you provide a payload which can be a userId and a secret. So, the token gets signed. After that, you need to verify the token in case you're trying to access some protected page that requires a token.
So, when you send a request to that protected route, like this:
router.get("/me", auth.verifyToken, usersController.identifyUser)
where identifyUser is a controller function that just identifies the logged in user by checking the userId(remember the user object contains the decoded object data).
how does the jwt know that that token which it is verifying is for that particular user and not for some other user who also sent a request at the same time? Does it store the token somewhere internally?
It's because of the payload that you give, which is unique to the user.
The Authentication token is stored in an Authentication Server, so when you send the Authentication token in your request header, the Authentication Server authenticated your client.
After being authenticated by Authentication Server, the client can now pass JWT to make API calls to the Application Server. Since client is allowed to make API calls, Application Server can verify the JWT token the client has sent and can process the API call.
Note that for making API calls, the client has to send a Authorization: Bearer <token> for each API call, which is stored at the server (aka Authorization Server)
the token is most store in the client
when the token verifying successfully, we will get some user info, etc account id, so we can use account id to find more user info in the database, and check the use is really exist
maybe it is useful for you?
You will typically sign the token with the user id when sending it from the server. So when the client then sends back that token you decode it and it will return the id to you. Which you then use to find the user in the data base

How do I refresh an access token Instagram when it has expired?

I use nodejs as a framework on the server side. I have successfully retrieved access_token data and used it.
but suddenly when I tried to request API it was rejected and after replacing the access_token it work again.
so as stated by the Instagram: Access tokens may expire at any time in the future.
how long access_tokens will be expire ?
Here my API request
export const getMediaRecent = (req,res)=>{
axios.get(`https://api.instagram.com/v1/users/self/media/recent/?access_token=${keys.instagram.access_token}`)
.then(result=>{
let imageInstagram = result.data.data.filter((d,i)=> i < 10).map(r=>{
return{
images:r.images,
caption:r.caption,
link:r.link
}
});
return res.status(200).json(imageInstagram);
})
.catch(err=>{
return res.status(400).json(err);
});
}
is there a way to refresh an access token when it has expired?

How to Refresh Firebase Session Cookie

I'm developing a web application using Node.js/Express.js for the backend and I use Firebase for user authentication, and to manage user registration etc I use Firebase Admin SDK.
When a user want to login I sign him in using Firebase Client SDK like this:
// Handling User SignIn
$('#signin').on('click', function(e){
e.preventDefault();
let form = $('#signin-form'),
email = form.find('#email').val(),
pass = form.find('#password').val(),
errorWrapper = form.find('.error-wrapper');
if(email && pass){
firebase.auth().signInWithEmailAndPassword(email, pass)
.catch(err => {
showError(errorWrapper, err.code)
});
}else {
showError(errorWrapper, 'auth/required');
}
});
Below this code, I set an observer to watch for when the user successfully sign in, After a successfull sign in I get a Firebase ID token which I send to an endpoint on the server to exchange it for a session cookie that has the same claims the ID token since the later expires after 1 hour.
// POST to session login endpoint.
let postIdTokenToSessionLogin = function(url, idToken, csrfToken) {
return $.ajax({
type: 'POST',
url: url,
data: {
idToken: idToken,
csrfToken: csrfToken
},
contentType: 'application/x-www-form-urlencoded'
});
};
// Handling SignedIn Users
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
user.getIdToken().then(function(idToken) {
let csrfToken = getCookie('csrfToken');
return postIdTokenToSessionLogin('/auth/signin', idToken, csrfToken)
.then(() => {
location.href = '/dashboard';
}).catch(err => {
location.href = '/signin';
});
});
});
} else {
// No user is signed in.
}
});
Sign in endpoint on the server looks like this:
// Session signin endpoint.
router.post('/auth/signin', (req, res) => {
// Omitted Code...
firebase.auth().verifyIdToken(idToken).then(decodedClaims => {
return firebase.auth().createSessionCookie(idToken, {
expiresIn
});
}).then(sessionCookie => {
// Omitted Code...
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({
status: 'success'
}));
}).catch(err => {
res.status(401).send('UNAUTHORIZED REQUEST!');
});
});
I have created a middle ware to verify user session cookie before giving him access to protected content that looks like this:
function isAuthenticated(auth) {
return (req, res, next) => {
let sessionCookie = req.cookies.session || '';
firebase.auth().verifySessionCookie(sessionCookie, true).then(decodedClaims => {
if (auth) {
return res.redirect('/dashboard')
} else {
res.locals.user = decodedClaims;
next();
}
}).catch(err => {
if (auth) next();
else return res.redirect('/signin')
});
}
}
To show user information on the view I set the decoded claims on res.locals.user variable and pass it to the next middle ware where I render the view and passing that variable like this.
router.get('/', (req, res) => {
res.render('dashboard/settings', {
user: res.locals.user
});
});
So far everything is fine, now the problem comes after the user go to his dashboard to change his information (name and email), when he submits the form that has his name and email to an endpoint on the server I update his credentials using Firebase Admin SDK
// Handling User Profile Update
function settingsRouter(req, res) {
// Validate User Information ...
// Update User Info
let displayName = req.body.fullName,
email = req.body.email
let userRecord = {
email,
displayName
}
return updateUser(res.locals.user.sub, userRecord).then(userRecord => {
res.locals.user = userRecord;
return res.render('dashboard/settings', {
user: res.locals.user
});
}).catch(err => {
return res.status(422).render('dashboard/settings', {
user: res.locals.user
});
});
}
Now the view gets updated when the user submits the form because I set the res.locals.user variable to the new userRecord but once he refreshes the page the view shows the old credentials because before any get request for a protected content the middle ware isAuthenticated gets executed and the later gets user information from the session cookie which contains the old user credentials before he updated them.
So far these are the conclusions that I came to and what I tried to do:
If I want the view to render properly I should sign out and sign in again to get a new Firebase ID token to create a new session cookie which is not an option.
I tried to refresh the session cookie by creating a new ID token from the Admin SDK but it doesn't seem to have this option available and I can't do that through the client SDK because the user is already signed in.
Storing the ID token to use later in creating session cookies is not an option as they expire after 1 hour.
I Googled the hell out of this problem before posting here so any help is so much appreciated.
I am facing a very similar scenario with one of my apps. I think the answer lies in these clues.
From Firebase docs
Firebase Auth provides server-side session cookie management for traditional websites that rely on session cookies. This solution has several advantages over client-side short-lived ID tokens, which may require a redirect mechanism each time to update the session cookie on expiration:
So they're hinting here that you want to manage the session and it's lifetime from the server.
Second clue is in the docs
Assuming an application is using httpOnly server side cookies, sign in a user on the login page using the client SDKs. A Firebase ID token is generated, and the ID token is then sent via HTTP POST to a session login endpoint where, using the Admin SDK, a session cookie is generated. On success, the state should be cleared from the client side storage.
If you look at the example code, the even explicitly set persistence to None to clear state from the client using firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
So they are intending there to be no state on the client beyond the initial auth. They explicitly clear that state and expect an httponly cookie so the client can't grab the cookie (which really is just the ID token) and use it to get a new one.
It is odd that there is no clear way of refreshing the token client-side but there it is. You can only really create a session cookie with a super long lifetime and decide on the server when to delete the cookie or revoke the refresh token etc.
So that leaves the other option: manage state client-side. Some examples and tutorials simply send the ID token from the client to the server in a cookie. The satte sits on the client and the client can use the ID token to use all firebase features. The server can verify the user identity and use the token etc.
This scenario should work better. If the server needs to kick the user then it can delete the cookie revoke the refresh token (a bit harsh admittedly).
Hope that helps. Another scheme would be to build custom tokens, then you have complete control.

Categories

Resources