Using https://github.com/netlify/gotrue-js to interface with netlify's authentication service (called "Identity") how frequently is it necessary to do the following:
const user = auth.currentUser();
const jwt = user.jwt();
jwt
.then(response => console.log("This is a JWT token", response))
.catch(error => {
console.log("Error fetching JWT token", error);
throw error;
});
Will the resulting JWT be valid forever? For the duration of the user's logged-in session? Or does it expire after a given amount of time?
Generally JWTs contain can (optionally) contain an exp (expiration) claim, that contains the time when it will expire.
I don't have experience with GoTrue, but according to their documentation you can configure the expiration, and it's set to a default value of 3600 seconds.
As the library also works with refresh tokens, you won't have to re-authenticate again after the token expires but use the refresh token to get a new access token.
Related
I wish to fetch the list of firebase projects of a signed-in user (not me) on my website.
I can't seem to request the firebase rest api correctly (endpoint) as I receive a 401 UNAUTHENTICATED error.
The steps I follow:
I am using firebase.auth().signInWithPopup on my browser to authenticate users. Here are the scopes used:
// Google provider
const provider = new GoogleAuthProvider();
// Additionnal scopes
provider.addScope("https://www.googleapis.com/auth/cloud-platform");
provider.addScope("https://www.googleapis.com/auth/firebase");
provider.addScope("https://www.googleapis.com/auth/datastore");
// Signin
signInWithPopup(auth, provider);
I am then using getIdToken method to get a token from the user.
const token = await auth.currentUser.getIdToken(true);
I use this token to request https://firebase.googleapis.com/v1beta1/projects:
const data = await window.fetch("https://firebase.googleapis.com/v1beta1/projects", {
headers: {
authorization: `Bearer ${token}`,
},
})
.then(res => res.json());
but I am always getting a 401 error:
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
}
What am I doing wrong?
To get a list of Firebase projects for a user you need an OAuth2 token for a user that is a collaborator on the projects. You are trying to use the ID token/JWT for a Firebase Authentication user, which is not the same.
Follow the documentation on generating an access token to get value that you can use to get a list of projects.
I just found the answer, the firebase SDK does give you an accessToken with the idToken. You can then use this token instead of the idToken for any of your REST api needs.
const result = await signInWithPopup(auth, provider);
// This gives you a Google Access Token. You can use it to access the Google API.
const credential = GoogleAuthProvider.credentialFromResult(result);
It seems there are many issues surrounding this with very few results e.g. here with no definitive answer.
Using the javascript Google Sign-in method, which returns the OAuth credential which includes accessToken, User, etc - the token which should be present in all authenticated requests to the hosted app (where security is crucial).
Is the following a good solution for vanilla Javascript web apps client-side (in one of the .js files) and what issues may arise from it?
// 1. Sign-in
firebase.auth()
.signInWithPopup(provider)
.then((result) => {
// 2. upon successful sign-in
Axios({
url: "/user/register-token",
method: "POST",
headers: {authorization: `Bearer ${idToken}`},
data: { //empty }
}).then((result) => {
// 3. successfully registered & authenticated, proceed to authenticated dashboard route
window.location.assign('/user/dashboard');
}).catch((error) => { // handle error});
}).catch((error) => {
// 4. Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(error);
});
Steps:
Sign-in with Google signin (using popup)
On successful popup signin, send token to GET /user/register-token which takes the token, verifies it (with some additional email domain checks) and saves it to the session cookie
Upon successful token registered, go to authenticated page
Handle error
My logic:
Since the session is stored server side, but there are session Id's attached to the current browsing session, I can thus use this as a "password of sorts" to verify the firebase session.
Concerns:
The only concern is using any verified firebase token and registering it. This however is address (where I check the associated email and confirm it is part of a firebase user collection & it has a recognized and accepted domain). Thus, they are not confirmed and sent back to the login page with a HTTP/403.
Is my logic sound, are there any potential issues?
My solution was on the right track, and matched up quite well with Firebase's cookie session.
For the record, I had alot of trouble storing the bearer token in my express session, thus searched for other solutions.
According to my steps, the solutions follows the same footprint, but with a slightly different implementation:
client-side changes
The ID Token (bane of my existence for atleast 24 hours) can be retrieved using this solution.
TL;DR (client side)
firebase.auth()
.signInWithPopup(provider)
.then((result) => {
firebase.auth().currentUser
.getIdToken()
.then((idToken) => // use this idToken, NOT result.credential.idToken
//...
Finally, send that to your server to create a session, endpoint is of your choosing e.g. POST: /user/createSession
Server-side changes
You need an endpoint (mine is /user/sessionLogin - part of userRouter - see more more info). Using the code below, you can also include CSRF (I couldn't get mind to work on the client side...), createSessionCookie using the idToken above - this works perfectly w/o any issues.
router.post('/sessionLogin', (req, res) => {
// Get the ID token passed and the CSRF token.
const idToken = req.body.idToken.toString();
// const csrfToken = req.body.csrfToken.toString();
// // Guard against CSRF attacks.
// if (csrfToken !== req.cookies.csrfToken) {
// res.status(401).send('UNAUTHORIZED REQUEST!');
// return;
// }
// Set session expiration to 5 days.
const expiresIn = 60 * 60 * 24 * 5 * 1000;
// Create the session cookie. This will also verify the ID token in the process.
// The session cookie will have the same claims as the ID token.
// To only allow session cookie setting on recent sign-in, auth_time in ID token
// can be checked to ensure user was recently signed in before creating a session cookie.
const auth = admin.auth();
auth.verifyIdToken(idToken).then(value => {
console.log("Token verified")
return auth.createSessionCookie(idToken, {expiresIn})
.then((sessionCookie) => {
// Set cookie policy for session cookie.
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
}).catch((error) => {
console.error(error);
res.status(401).send('UNAUTHORIZED REQUEST!');
});
}).catch(reason => {
console.error("Unable to verify token");
console.error(reason);
res.status(401).send('INVALID TOKEN!');
});
});
Finally, to validate your requests and check cookie status, you need to verify your requests to the server using (or request a new login session by redirecting to /user/login):
exports.authenticate = (req, res, next) => {
const sessionCookie = req.cookies.session || '';
// Verify the session cookie. In this case an additional check is added to detect
// if the user's Firebase session was revoked, user deleted/disabled, etc.
return admin
.auth()
.verifySessionCookie(sessionCookie, true /** checkRevoked */)
.then((decodedClaims) => {
req.user = decodedClaims;
next();
})
.catch((error) => {
console.error(error);
// Session cookie is unavailable or invalid. Force user to login.
req.flash("message", [{
status: false,
message: "Invalid session, please login again!"
}])
res.redirect('/user/login');
});
};
see this for more information.
I want to force-refresh the AWS cognito token in the client, so that as soon as a user logs in the app immediately uses the refresh token to get a new access token (with longer exp time). I've read about initiateAuth and cognitoUser.refreshSession() methods, but I'm not sure which one I need to use? I'm trying the below method and I am getting a new token back but it's always just valid for 1 more hour (at least that is what I see when I check the exp time on the token). I want to get a token that is valid for 30 days (which is what I have configured in cognito). Any help will be greatly appreciated!
const getTokens = function(session) {
return {
accessToken: session.getAccessToken(),
idToken: session.getIdToken(),
refreshToken: session.getRefreshToken()
};
cognitoUser.refreshSession(refreshToken, (err, session) => {
if (err) {
console.log(err);
} else {
const tokens = getTokens(session);
console.log(tokens);
localStorage.setItem("sessionTokens", JSON.stringify(tokens));
}
}
The ID and Access token in Cognito are valid for 1 hour and this is not configurable. You mentioned you have configured the tokens to last for 30 days, this is the validity/expiry time of your refresh tokens.
Calling certain methods on the client side SDKs (Amplify or identity SDK) will automatically check the validity and expiry time of the tokens and refresh them if needed.
This has now changed as can be seen here:
https://aws.amazon.com/about-aws/whats-new/2020/08/amazon-cognito-user-pools-supports-customization-of-token-expiration/
Cognito recently added options to configure the token validity.
Refresh tokens can have a TTL from 60 minutes to 365 days.
ID tokens and Access tokens can have a TTL from 5 minutes to 1 day
just look in the details of your user pool app client, the new fields are in there for easy configuration.
I'm creating a custom token on backend server while login and below is the code:
UserSchema.methods.generateAuthToken = function() {
var user = this;
var access = "auth";
return firebaseAdmin.default
.auth()
.createCustomToken(user._id.toHexString())
.then(function(token) {
user.tokens = user.tokens.concat([{ access, token }]);
return user.save().then(() => {
return token;
});
});
};
and storing this token inside mongodb and sending it back to client via header named "x-auth" and then stored it inside cookies.
On client side, using this token to signin like below:
axios({
url: "/api/users/login",
method: "POST",
data: {
email: data.email,
password: data.password
}
}).then(res => {
Cookies.set("x-auth", res.headers["x-auth"], { expires: 7 });
return firebase
.auth()
.signInWithCustomToken(res.headers["x-auth"])
.then(response => {
console.log("CA", response);
response
.getIdToken()
.then(idToken => {
Cookies.set("idToken", idToken, { expires: 7 });
})
.catch(e => {});
dispatch(
login({
token: Cookies.get("x-auth")
})
);
});
});
Now in order to call an api to fetch all users, I'm sending these tokens, custom token and idToken back to server:
const authenticate = (req, res, next) => {
const token = req.header("x-auth");
const idToken = req.header("idToken");
User.findByToken(token, idToken)
.then(user => {
if (!user) {
return Promise.reject({
message: "no user found with the associated token!"
});
}
req.user = user;
req.token = token;
next();
})
.catch(e => {
res.status(401).send(setErrorMessage(e.message));
});
};
And findByToken is as below:
UserSchema.statics.findByToken = function(token, idToken) {
var User = this;
return firebaseAdmin.default
.auth()
.verifyIdToken(idToken)
.then(function(decodedToken) {
var uid = decodedToken.uid;
return User.findOne({
_id: uid,
"tokens.access": "auth",
"tokens.token": token
});
})
.catch(function(e) {
return Promise.reject(e);
});
};
Why do I have to send both these tokens for authorization, is there anything wrong with the concept here.
https://firebase.google.com/docs/auth/admin/verify-id-tokens
Warning: The ID token verification methods included in the Firebase Admin SDKs are meant to verify ID tokens that come from the client SDKs, not the custom tokens that you create with the Admin SDKs. See Auth tokens for more information.
Please clarify if I can verify the custom token instead of idToken to retrieve userId and match it up with DB, instead of using different tokens for one purpose or I'm doing something wrong here and custom token should not be stored inside DB, and there is some other approach to it.
And now after sometime when I try to fetch all users, it says:
Firebase ID token has expired. Get a fresh 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.
When working with tokens, you need to differentiate each token and what it is used for, also each token has related properties.
Custom Tokens: These tokens will be generated in the server (as you are doing) and are used in the client side to authenticate the client. These tokens expire after one hour.
Session ID Token: (I'm calling it "Session ID Token" just to differentiate it) When the client autheticates with any of the authentication providers, the SDK will exchange the information for an ID token used for the session. The same applies for custom tokens, where the SDK exchanges the custom token for an ID Token. ID Tokens are also short live and will expire after one hour. When getting the ID Token, the SDK also receives a refresh Token which is used for refreshing the session ID Token. The ID token is used when doing authenticated requests to Firebase.
ID Token for verification: To get this token, you will need to call the function "getIDToken" (for Web). This function will return an ID Token that can be used only to verify requests coming from the client to your server. Similar to the other tokens, this one expires after one hour. When calling the getIDToken, the function will request a new one if the current is expired.
To verify the ID Token, you must use the mentioned token. If you try to use the session ID Token or the Custom token, you will get errors. The same applies if you try to use an expired ID Token.
All the calls and tasks to refresh the ID Tokens are handled by the SDKs behind the scenes.
It is not a good practice to store these tokens as they will expire after one hour, is better if you call the appropriate functions to get the latets tokens and pass the correct ID Token to be verified.
Lastly, you can find in these links the properties used for generating the custom token and the ID Token for verification.
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.
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.