How to show user information (name or avatar) on appBar? - reactjs

I want to show logged user information (name, avatar, role) on appBar. I created some custom components : layout, appBar and userMenu How can I pass the logged user data to the userMenu or appBar component?

I believe that in the getIdentity method of your Auth provider you want to return an object with this information in your Promise.resolve. So it is somewhat indirect and not done within a custom AppBar itself.
Here is a snippet example from an auth provider:
getIdentity: () => {
return Promise.resolve({
id: userName,
fullName: userFullName,
avatar: <Avatar />
});
The Avatar aspect is optional of course and React-Admin will place its own avatar there if you leave that out.

One approach would be retrieving the user information from the authentication storage you are using on the client-side.
In my case, I'm showing user initials instead of the standard avatar in the component by pulling the information from the JWT token stored on localStorage.
decodeJwt(localStorage.getItem('token')).fullname
Probably not the best approach but worked for my use case.

See https://github.com/marmelab/react-admin/issues/7042
This is what worked for me in my AuthProvider. I am using session-based authentication and don't have a jwt client side - so I expose an API on the server that provides the logged in user info. I don't have an avatar so I pass the user initials (only the first letter appears on the page)
getIdentity: async () => {
const userinfo = await UserService.getUserInfo();
const {id: id, userName: fullName, firstLastInitials: avatar} = userinfo;
console.log(`getIdentity UserService user name is ${fullName}'`);
return Promise.resolve({ id, fullName, avatar });
}
and
const getUserInfo = async () => {
const apiUri = HostApiServer(); // GET SERVER URI
const mainResponse = await fetch(`${apiUri}/loggedInUser`, {
method: 'GET'
});
const resp = await mainResponse.json();
console.log(`UserService - GOT user info from ${apiUri}`);
console.log(JSON.stringify(resp));
return resp;
};
const UserService = {
doLogin,
doLogout,
isLoggedIn,
getToken,
getUserInfo
};

Related

How do I get an object from local storage after json.stringify()

I am currently creating a login page with ReactJS. This page utilises an API which has an 'authorise' function confirming whether the entered details are authorised to use the API. To handle the API calls, I have created a class which has a function for each possible API call.
If the user is able to login successfully (if the authorise function returns a status of 200) they are redirected to a 'ProjectSelect' page/component. From this Project Select page the user will be able to select a specific project from their project list.
Now, here is where the problem lies. I need to now use the API instance from the 'Login' component in the 'ProjectSelect' component. The way in which I am currently trying to do this involves storing it in local storage as such:
Login.jsx:
import {API} from 'filepath';
const api = new API();
async function loginUser(email, password, appid) {
return api.authorise(email, password, appid);
}
export default function Login() {
const classes = useStyles();
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const [appid, setAppId] = useState();
const handleSubmit = async e => {
e.preventDefault();
const response = await loginUser(
email,
password,
appid
);
console.log(response);
if (response.status === 200) {
swal("Success", "Logged In", "success", {
buttons: false,
timer: 2000,
})
.then((value) => {
localStorage.setItem('access_token', response.data.access_token);
localStorage.setItem('api', JSON.stringify(api));
window.location.href = "/bimpluslogin";
});
} else {
swal("Failed", "Login Failed", "error");
}
}
ProjectSelect.jsx:
const api = JSON.parse(localStorage.getItem('api'));
let allProjects = api.getAllProjects(); // Error occurs here ('getAllProjects()' does not exist)
I am aware that if you JSON.stringify() an object it will only take the key values leaving it basically unusable when parsed back through the JSON.
What I've tried:
I have tried to create a new API instance in the ProjectSelect component and storing the auth details in local storage however that causes a lot of issues and doesn't allow me to use all of the functions.
I would appreciate any suggestions on how to store an object from one component and then use that exact same object in another component. Thanks in advance.

How to get current provider of session in Next-auth

In Next-auth, We can get session related info like user: {name, email .. etc}
something as follows:
import { useSession } from "next-auth/client"
export default function Component() {
const [session, loading] = useSession()
if (session) {
return <p>Signed in as {session.user.email}</p>
}
return Sign in
}
I want to get also the current provider name in my component to be used. For you your information, next-auth supports login by many providers as Facebook, Twitter .. etc
For example, if the user logged in via Twitter Api, I want to get this piece of info and print it in his profile page.
Sources:
https://next-auth.js.org/v3/getting-started/client#usesession
Callbacks can be used for passing additional data to session object.
Provider details are provided the first time user signs in.
You can use jwt callback to store data in jwt cookie.
In [...nextauth].js :
const callbacks = {}
callbacks.jwt = async function jwt(token, user , account) {
if (user) {
token = { id: user.id , provider:account.provider , ...moreData}
}
return token
}
const options = {
providers,
callbacks
}
Note: You also need to use session callback for passing the token data to useSession hook.
callbacks.session = async function session(session, token) {
session.user = {
provider : token.provider,
id: dbUser.id,
profile: dbUser.profile,
}
return session
}
export default (req, res) => NextAuth(req, res, options)
This way provider will be stored in the token for subsequent requests.
Learn more about callbacks here : Callbacks

Send Id to another component after signin user react js

I used Firebase for user authentication. I set functions for register users. then I created a signin function. When the user signs user redirects to user profile edit mode. Now I want to retrieve data when signing in user his/her particular data. I need to get a firebase user id to pass the another component when sign in. I try to get firebase user id and pass id to another component.Then I try to pass id to the backend using get function. Then I think i can retrive data for paticular user
signin function
async onSubmit(e) {
e.preventDefault();
const email = this.state.todo_email;
const password = this.state.todo_password;
try {
const signInresponse = await firebaseAuth.signInWithEmailAndPassword(email, password);
history.push('/User/Directory');
} catch (e) {
console.error(e);
}
Edit
I may have misread your question 😬
Your signInresponse should return a UserCredential which has a user property. The user prop can then be used to grab a bunch of other props.
So, signInresponse.user.uid will get you the uid...
I personally like the Promise structure and I destructure the user out, because it feels more event-driven.
firebaseAuth.signInWithEmailAndPassword(email, password)
.then(({ user }) => {
// do stuff
setUID(user.uid);
setUserName(user.displayName);
})
.catch((error) => {
handleWarning(error);
});
As an aside, check out the react-firebase-hooks. It can make things a little cleaner.
I hope this better answers your question.
You could use React.Context as a hook and expose it to your app as need. The usage is exactly like the useState() hook.
The main Context hook can be in a file:
// UserContext.js
import { createContext } from "react";
const userContextProvider = createContext({
userContext: {
name: null,
uid: null,
},
setUserContext: () => {},
});
export default userContextProvider;
In your base-level App you import the hook above, and wrap your project:
// App.js
import userContextProvider from "./api/UserContext";
const [userContext, setUserContext] = useState({ name: null, uid: null });
const value = { userContext, setUserContext };
return (
<userContextProvider.Provider value={value}>
<Router>
<Route path={} component={} ...
// ...
And then in the components you want to set or read your uid or name or whatever value:
// RandomComponent.js
import React, { useState, useContext } from "react";
import userContextProvider from "./api/UserContext";
const { userContext, setUserContext } = useContext(userContextProvider);
Then you can use userContext as your getter and setUserContext as your setter just like useState()

Pass Data to Firebase auth.user().onCreate Cloud Function

I'm testing out firebase cloud functions and I'm curious as to how to pass data from my form to the cloud function. I've created a registration form that accepts several fields - my plan is to use firebase to create an auth account and have a cloud function that runs whenever a auth account is created. This function will create a user document inside Firestore.
Code below gives an idea of my frontend setup
import React from "react"
import firebase from "firebase"
// create some config for firebase
const app = firebase.initializeApp(config)
const auth = app.auth()
function Form({onSubmit}){
function handleSubmit(e){
e.preventDefault()
const {email, password, firstName, lastName, age} = e.target.elements
const user = {email: email.value, password: password.value, firstName: firstName.value, lastName: lastName.value, age: age.value}
onSubmit().then(() => {}, (err) => {})
}
return <form onSubmit={handleSubmite}>
{/*form fields..*/}
</form>
}
// Register function for firebase
function register(email, password){
return auth.createUserWithEmailAndPassword(email, password)
}
// Parent for form component
function Container(){
return <Form onSubmit={register}/>
}
This is what my cloud function looks like:
const functions = require("firebase-functions")
const admin = require("firebase-admin")
admin.initializeApp()
const db = admin.firestore()
const createUser = (userData, context) => {
const { email } = userData
console.log(userData)
return db
.collection("users")
.doc()
.set({
email,
firstName: "TEST",
})
.catch(console.error)
}
module.exports = {
authOnCreate: functions.auth.user().onCreate(createUser),
}
Any idea how to do this? Am I thinking about this incorrectly? I'm a bit new to firebase.
TLDR; How do I send custom data from a form to a cloud function that will create a user in firestore on a new user being created in auth?
my plan is to use firebase to create an auth account and have a cloud function that runs whenever a auth account is created. This function will create a user document inside Firestore.
This is not possible. Since your Cloud Functions triggers on auth.user().onCreate it only has access to the information that is available with that event. You can't pass additional information to a background function.
The most common solutions are:
Write the document after the user is created, either directly from the client or by having the client call another Cloud Function directly.
Pass all data that is needed to create the user and document to a Cloud Function, and then let the Cloud Function handle both the creation of the user account and the document.

AWS Cognito/React.js newPasswordRequired Challenge

I'm working on a login flow for my web app built in React and I'm using AWS Cognito for user management. I'm working on the login flow, and I have a use case where a user is created via the AWS Console and a temporary password is provided to the user. When the user goes to login to my application for the first time, AWS Cognito returns a newPasswordRequired Challenge, and the user is forced to change their password.
I'm using the amazon-cognito-identity-js API to authenticate the user. The docs for that can be found here. I have the newPasswordRequired callback function setup just like the docs instruct, but I'm struggling to figure out the best way to gather the new password from the user using React within the newPasswordRequiredfunction. I initially used prompt() within the function to get the inputs, but I would like the app to flow to a new page where the user can enter a new password, confirm new password, and login to the app. That new page should be able to call the cognitoUser.completeNewPasswordChallenge() that is required to update the new password. Please HELP! Here's my code below:
onFormSubmission = (username, password) => {
const poolData = {
UserPoolId : AWSConfig.cognito.USER_POOL_ID,
ClientId : AWSConfig.cognito.APP_CLIENT_ID
}
const userPool = new CognitoUserPool(poolData);
const userData = {
Username: username,
Pool: userPool
}
const authenticationData = {
Username : username,
Password : password
}
const authenticationDetails = new AuthenticationDetails(authenticationData);
const cognitoUser = new CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
/*Use the idToken for Logins Map when Federating User Pools with identity pools or when passing through an Authorization Header to an API Gateway Authorizer*/
console.log('idToken + ' + result.idToken.jwtToken);
},
onFailure: function(err) {
console.log(err);
},
newPasswordRequired: function(userAttributes, requiredAttributes) {
// User was signed up by an admin and must provide new
// password and required attributes, if any, to complete
// authentication.
// userAttributes: object, which is the user's current profile. It will list all attributes that are associated with the user.
// Required attributes according to schema, which don’t have any values yet, will have blank values.
// requiredAttributes: list of attributes that must be set by the user along with new password to complete the sign-in.
*** THIS IS WHERE I WANT REACT TO RENDER A NEW PAGE TO GET THE NEW PASSWORD***
// Get these details and call
// newPassword: password that user has given
// attributesData: object with key as attribute name and value that the user has given.
cognitoUser.completeNewPasswordChallenge(pw, userAttributes, this);
}
});
}
render() {
return (
<div>
<LoginScreenComponent isInvalidForm={this.state.isInvalidForm} onFormSubmission={this.onFormSubmission}/>
</div>
)
}
I had exactly the same problem! Here is my solution:
Login.js react container can render two different components. <NewPassswordForm /> is to ask a new password, <LoginForm /> is for common login. According to isFirstLogin flag you decide which one to render.
Since you have the cognito user in this.state.user you can use it to call completeNewPasswordChallenge to finish the login flow:
handleLogin = (username, password) => {
const authDetails = new AuthenticationDetails({
Username: username,
Password: password,
});
const userData = {
Username: username,
Pool: getUserPool(),
Storage: getStorage(),
};
const cognitoUser = new CognitoUser(userData);
cognitoUser.authenticateUser(authDetails, {
onSuccess: () => {
// login
}
newPasswordRequired: userAttr => {
this.setState({
isFirstLogin: true,
user: cognitoUser,
userAttr: userAttr,
});
},
});
};
changePassword = (newPassword) => {
const cognitoUser = this.state.user;
const userAttr = this.state.userAttr;
cognitoUser.completeNewPasswordChallenge(newPassword, userAttr, {
onSuccess: result => {
// login
}
});
};
render() {
return (
<div>
{this.state.isFirstLogin ? (
<NewPassswordForm changePassword={this.changePassword} />
) : (
<LoginForm handleLogin={this.handleLogin} />
)}
</div>
);
}

Resources