Get User Display Name from Firebase VerifyToken (part2) - node.js

I have the same situation as the other post with the same title. I have a custom backend (node+express) and I am able to update the displayName on the front end. However, when looking at the DecodedIdToken on the backend, there's no "name" when registering the user. I need the display name of the user so it can be synced with other clients on the backend.
If I sign out the newly registered user and log back in, the DecodedIdToken now shows the "name" on the backend.
Client side code:
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(dataBeforeEmail => {
firebase.auth().onAuthStateChanged(function(user) {
user.sendEmailVerification();
user.updateProfile({displayName: displayName})
});
})
.then(dataAfterEmail => {
firebase.auth().onAuthStateChanged(async function(user) {
if (user) {
// Sign up successful
dispatch({
type: REGISTER_SUCCESS,
payload:user
});
const header = await tokenConfig();
try{
axios
.post('/api/auth/',{}, header)
.then(res=>
dispatch({
type: REGISTER_SUCCESS,
payload: res.data
})
(console.log(res.data))
)
}
catch(err) {
dispatch({
type: REGISTER_FAIL,
payload:
"Something went wrong, we couldn't create your account. Please try again."
});
};
export const tokenConfig = async () => {
const user = firebase.auth().currentUser;
const token = user && (await user.getIdToken());
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
};
return config;
};
Is there a way to have it show without having the user log out and log back in?

This is because of how the client SDK is handling the ID tokens. ID tokens are cached up to an hour. So you have to do one of the following before any changes to the user account are reflected on the ID token:
Wait for the current ID token to expire, and the SDK to automatically fetch a new one.
Sign-out and sign-in, which cycles out the ID token.
Explicitly request an ID token refresh on the client SDK. If you're using the JS SDK this will look something like this:
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true)
For more details:
https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients
https://firebase.google.com/docs/auth/admin/manage-sessions

Related

I would like to update only one state and return the updated state in reactjs

I have a nodejs and reactjs web application. Though I am still learning reactjs. I have a system where users login, I captured the user's name, id and token coming from my nodejs: I am using react useContext() to mange the app's state.
Login:
dispatch({type: "LOGIN_START"})
try{
const response = await axios.post("/auth/login", {
username: userRef.current.value,
password: passwordRef.current.value
});
console.log(response.data.token)
dispatch({type:"LOGIN_SUCCESS", payload: response.data.user, tokenData: response.data.token});
}catch(err){
dispatch({type: "LOGIN_FAILURE"})
};
I saved the response.data.user and response.data.token in my localstorage.
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
token: JSON.parse(localStorage.getItem("token")) || null,
export const Context = React.createContext();
export const ContextProvider = ({children}) =>{
const [state, dispatch] = useReducer(Reducer, INITIAL_STATE);
//useEffect to enable the user details to be stored in their local storage
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user, ));
localStorage.setItem("token", JSON.stringify(state.token, ));
}, [state.user], [state.token]);
return(
<Context.Provider value={{
user:state.user,
token: state.token,
dispatch,
}}>
{children}
</Context.Provider>
)
};
I created the login action like this which enabled me to capture the user's details and token:
export const LoginSucess = (user, token) => ({
type: "LOGIN_SUCCESS",
payload: user,
tokenData: token
});
And the useReducer was written like this:
case "LOGIN_SUCCESS":
return{
user: action.payload,
token: action.tokenData,
};
If I want to update the token alone, it does't work as I wanted. Here is the code:
case "UPDATE_TOKEN":
return{
...state,
token: action.tokenData,
isFetching: false,
error: true,
};
It just does't update at all. If I remove ...state, it will update the token but the state.user will become undefined.
If I dont separate the token and user's details during sign-in action, it becomes a problem when the user wants to update their profile. Surely, the user wouldn't be updating their token when updating their profile and this will return user's details without a token. Token is updated via refresh token route or when user logs-in again.
I want to separate the token state from the user's name and id state. The token changes every 15 minutes, needs to have it own state. How do I implement this?
I decided to create another usecontext and usereducer to handle the token state since it will always change every 15 minutes.

How to Authenticate the logged in user from a MERN application inside React-Redux And Check with every request?

I am working on my second React/Redux application (I'm new to Redux) and also the first MERN stack app altogether.
Currently, I am having an Authentication problem on the Front-End side in React/Redux Which goes like this:
I have established a protect middleware in my Back-End to check if the user which logged in is always logged in with valid JWT-Token or not by setting the Token inside the Browser Cookie with HttpOnly flag enabled {no secure flag yet but I will enable that too in production step}
The main problem is that once I log in the application, everything is fine and if the user is validated without any errors then inside the authReducer, the isAuthenticated property will be set to true and then logged in user's data is passed to the redux store to be used. but once I redirect to the main feed page of the app everything is gone the isAuthenticated is now false and user data is null so the app crashes.
I know that I must call the auth protected route every time I send a request to the server but I am stuck because it needs some piece of the logged-in user like username or ID to be sent along with the each request to validate it and I can't store them in local storage because of safety issues. { storing sensitive data in local storage is not a good practice}
this is log in function in Redux Actions:
export const loginUser = (loginData) => async (dispatch) => {
try{
setLoading();
const res = await axios({
method: "post",
url: "/users/login",
data: {loginData}
});
dispatch({
type: TYPES.LOGIN_SUCCESS,
payload: {
status: res.data.status,
token: res.data.token,
user: res.data.data.user
}
});
// This below code will set the logged in user after successful login
getLoggedInUser(res.data.data.user.username);
}catch(error){
dispatch({
type: TYPES.LOGIN_FAIL,
payload: {
status: error.response.data.status,
message: error.response.data.message
}
});
}
};
as you see after the successful log in, the token and user data is dispatched to store but after component reload they are gone so I can't re-Authenticate the logged in user.
this is my getLoggedInUser redux action function:
export const getLoggedInUser = (userName) => async (dispatch) => {
try{
setLoading();
const res = await axios.get(`/auth/${userName}`, {
// headers:{
// Cookies: `jwt=${token}`
// }
});
dispatch({
type: TYPES.GET_LOGGED_IN_USER,
payload: res.data.data.loggedInUser
});
}catch(error){
dispatch({
type: TYPES.AUTH_ERROR,
payload: {
status: error.response.data.status,
message: error.response.data.message
}
});
}
};
Any solutions?

Not able to watch Admin Users Directory using `google-admin-sdk`

I am trying to connect to the G-Suite's User directory using the google-admin-sdk. I am using an API Key for authorization and I am not able to reach a successful execution.
Here is the code snippet that I'm using:
import { google } from 'googleapis';
import uuid from 'uuid/v4';
const API_KEY = 'my api key goes here';
google.admin({
version: 'directory_v1',
auth: API_KEY
}).users.list({
customer: 'my_customer',
maxResults: 10,
orderBy: 'email',
}, (err, res: any) => {
if (err) { return console.error('The API returned an error:', err.message); }
const users = res.data.users;
if (users.length) {
console.log('Users:');
users.forEach((user: any) => {
console.log(`${user.primaryEmail} (${user.name.fullName})`);
});
} else {
console.log('No users found.');
}
});
Output:
Login Required
Can someone tell me what I am doing wrong here?
Also, how do I proceed further for listening to the events emitted by the Google API?
---UPDATE---
Here is the snippet that works for me now:
import { JWT } from 'google-auth-library';
import { google } from 'googleapis';
// Importing the serivce account credentials
import { credentials } from './credentials';
const scopes = ['https://www.googleapis.com/auth/admin.directory.user'];
const adminEmail = 'admin_account_email_address_goes_here';
const myDomain = 'domain_name_goes_here';
async function main () {
const client = new JWT(
credentials.client_email,
undefined,
credentials.private_key,
scopes,
adminEmail
);
await client.authorize();
const service = google.admin('directory_v1');
const res = await service.users.list({
domain: myDomain,
auth: client
});
console.log(res);
}
main().catch(console.error);
--- Bonus Tip ---
If you face any Parse Errors while using other methods of the directory, remember to JSON.stringify the request body. For example, on the admin.users.watch method:
// Watch Request
const channelID = 'channel_id_goes_here';
const address = 'https://your-domain.goes/here/notifications';
const ttl = 3600; // Or any other TTL that you can think of
const domain = 'https://your-domain.goes';
const body = {
id: channelID,
type: 'web_hook',
address,
params: {
ttl,
},
};
// Remember to put this in an async function
const res = await service.users.watch({
domain,
customer: 'my_customer',
auth: client, // get the auth-client from above
event: 'add'
}, {
headers: {
'Content-Type': 'application/json'
},
// This is the important part
body: JSON.stringify(body),
});
As you can see in the official documentation, every request sent "to the Directory API must include an authorization token". In order to authorize your request, you have to use OAuth 2.0.
You are providing an API key instead, which is not appropriate for this process. API keys are usually used for accessing public data, not users' private data as in your current situation.
You should follow the steps provided in the Node.js Quickstart instead:
First, obtain client credentials from the Google API Console.
Second, authorize the client: obtain an access token after setting the user credentials and the appropriate scopes (a process accomplish in functions authorize and getNewToken in the Quickstart).
Finally, once the client is authorized, call the API (function listUsers).
Update:
If you want to use a Service Account for this, you will have to follow these steps:
Grant domain-wide delegation to the Service Account by following the steps specified here.
In the Cloud console, create a private key for the Service Account and download the corresponding JSON file. Copy it to your directory.
Use the Service Account to impersonate a user who has access to this resource (an Admin account). This is achieved by indicating the user's email address when creating the JWT auth client, as indicated in the sample below.
The code could be something along the following lines:
const {google} = require('googleapis');
const key = require('./credentials.json'); // The name of the JSON you downloaded
const jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
['https://www.googleapis.com/auth/admin.directory.user'],
"admin#domain" // Please change this accordingly
);
// Create the Directory service.
const service = google.admin({version: 'directory_v1', auth: jwtClient});
service.users.list({
customer: 'my_customer',
maxResults: 10,
orderBy: 'email',
}, (err, res) => {
if (err) return console.error('The API returned an error:', err.message);
const users = res.data.users;
if (users.length) {
console.log('Users:');
users.forEach((user) => {
console.log(`${user.primaryEmail} (${user.name.fullName})`);
});
} else {
console.log('No users found.');
}
});
Reference:
Directory API: Authorize Requests
Directory API: Node.js Quickstart
Delegate domain-wide authority to your service account
Google Auth Library for Node.js
I hope this is of any help.

Saving JWT token to local storage

I want to save a JWT token into local storage in order to authenticate routes. My code is below but when this route is hit the browser just sits on loading and then says this page isnt working. Removing the localStorage.setItem() makes it work. Im wondering why this is happening. Thanks.
} else {
bcrypt.compare(password, user.password).then(Matched => {
if (Matched) {
//Create the payload for JWT to code
const payload = { id: user.id, name: user.name, email: user.email };
jwt.sign(
payload,
keys.JWT_KEY,
{ expiresIn: 3600 },
(err, token) => {
**localStorage.setItem("token", token);
res.redirect("/");**
}
);
} else {
Because localStorage.setItem("token", token) doesn't exist in nodejs, so the app will crash on this line and res.redirect("/"); is never executed, so the response is never sent back and your browser hangs while waiting for the response.
To fix it, send token back to client using res.json({ token: token }); and run localStorage.setItem("token", token); in the browser.

React-Redux app with Node backend shows previous user's info on login

I've got an app which requests a bunch of plots from the backend based on the present user's company code. The problem is that when a user logs in, it shows him the previous user's plots. On refresh, the app shows the current user's plots, as it is supposed to.
I'm using JWT stored in local storage for authentication. Looking in local storage, the user's information is properly stored and disposed of on logout.
The API endpoint:
if (req.user.companyCode === 'admin') {
plotStatus = await PlotStatus.find({}).lean();
} else {
plotStatus = await PlotStatus.find({
company: req.user.companyCode
}).lean();
}
if (!plotStatus) {
throw new Error('Plot Statuses not found');
} else {
res.send(plotStatus);
}
The Redux action:
export function fetchPlotStatuses() {
const user = JSON.parse(localStorage.getItem('user'));
return function(dispatch) {
axios
.get(`${ROOT_URL}/plotstatuses`, {
headers: { authorization: user.token }
})
.then(response => {
dispatch({
type: FETCH_PLOTSTATUSES,
payload: response.data
});
});
};
}

Resources