I have 2 subdomains and 1 main page: product.com, expert.product.com and user.product.com.
After user login in the main page: product.com, my code will check which role of user belongs to: expert, user. If user's role is expert, it will direct user to expert.product.com.
I save user to local storage. I would like to use cross-domain local storage in Reactjs. However, I do not know how to use it :(.
Where should I use cross-domain local storage: in repository of main page, expert.product.com or user.product.com? Do I need to set up from my server or cross-domain local storage can be done in front end side? Could anyone help me for this case?
Set localstorage
handleSubmit = event => {
event.preventDefault();
api
.post(`myapi, {
identifier: this.state.userSignIn.emailSignIn,
password: this.state.userSignIn.passwordSignIn,
})
.then(response => {
localStorage.setItem('userData', response.data.user);
localStorage.setItem('user', JSON.stringify(response.data));
this.setState({ auth: true, open: false, openSignin: false });
this.props.history.push('/profile');
})
.catch(error => {
console.log('An error occurred:', error);
});
};
Get LocalStorage
componentDidMount() {
if (localStorage.getItem('userData')) {
const userObject = JSON.parse(localStorage.getItem('user'));
const jwt = userObject.jwt;
const config = {
headers: { 'Authorization': `bearer ${jwt}` },
};
}
Related
I have been trying to figure this out for weeks and either can't seem to understand the documentation, or something. I appreciate any help you can give.
I am using the Firebase SDK
I have my server-side route, in which I can access the token and could send it to the front:
const admin = require("firebase-admin")
admin.initializeApp()
exports.loginRoute = (req, res) => {
const user = {
email: req.body.email,
password: req.body.password
}
const { valid, errors } = validateLoginData(user)
if (!valid) {
return res.status(400).json(errors)
}
admin
.auth()
.signInWithEmailAndPassword(user.email, user.password)
.then((data) => {
console.log(data.user.refreshToken, "refresh token")
return data.user.getIdToken(true)
})
.then((token) => {
return res.json({ token })
})
.catch((err) => {
console.error(err)
if (err.code === "auth/user-not-found") {
return res.status(400).json({ general: "User not found" })
} else if (err.code === "auth/wrong-password") {
return res
.status(400)
.json({ password: "User credentials don't match" })
} else {
res.status(500).json({
error: "Something went wrong, please try again."
})
}
})
}
Here is where I could use the refresh token (on the front end) to fetch a new authentication token, but I can't figure out how to create a route to do this:
if (token) {
const decodedToken = jwtDecode(token)
if (decodedToken.exp * 1000 < Date.now()) {
localStorage.setItem("Authentication", false)
//axios request to persist authentication would go here
}
}
Does anyone have a route that would work, or advice on what to do?
EDIT
const login = async (credentials) => {
let token
await axios
.post("/api/login", credentials)
.then((res) => {
token = res.data.token
const FBIdToken = `Bearer ${token}`
localStorage.setItem("token", token)
localStorage.setItem("FBIdToken", FBIdToken)
localStorage.setItem("Authentication", true)
context.setAuthenticated((prev) => true)
})
.then(() => {
context.getUserData()
})
.then(() => {
context.setUserState((prevUserState) => ({
...prevUserState,
token
}))
})
.catch((err) => {
context.setUserErrors((prev) => ({
...prev,
errors: err.response.data
}))
})
history.push("/")
}
Observer (client-side):
firebase.auth().onAuthStateChanged((user) => {
if (user) {
firebase
.auth()
.currentUser.getIdToken(/* forceRefresh */ true)
.then((idToken) => {
const FBIdToken = `Bearer ${idToken}`
localStorage.setItem("FBIdToken", FBIdToken)
})
.catch((err) => {
console.log(err)
})
} else {
localStorage.removeItem("FBIdToken")
}
})
If you sign in with the Firebase Authentication JavaScript SDK in the client-side code, it already persists the user's sign-in state, and tries to restore it when you reload the page. You shouldn't have to do anything for that yourself.
It seems like you were using the same SDK in a server-side environment though, which is quite unusual. If you want to mint tokens yourself in a server-side environment, you should use the Firebase Admin SDK to do so. You can then send that token back to the client, and use it to sign in to Firebase Authentication there.
But for the vast majority of use-cases, I recommend using the Firebase Authentication SDK in your client-side code, so that the SDK managed refreshing of the token for you. If you then want to pass the token to the server, you can use getIdToken() as you do now. You can also monitor ID token generation, or more commonly monitor if a user's sign-in session is restored as shown in the first example of the documentation on detecting the current user.
I'm trying to get sessions to work with a React front-end and an express + connect-mongo using MongoStore back-end.
Handle Register Function
async function handleRegister(evt){
//Prevent default form redirect.
evt.preventDefault();
//Create a new user objec to pass into axios
const user = {
username: username,
password: password
}
//Send axios post request to nodeJS API.
await axios.post("http://localhost:5000/users/register",user)
.then((res) => {
history.push({
pathname: '/',
state: res.data
});
})
.catch((err) => {
console.log(err);
});
//Push react history back to index page.
}
Handle Login function
const handleLogin = async (evt) => {
//Prevent default form submission
evt.preventDefault();
const loginDetails = {
username: username,
password: password,
}
//send login request to api
await axios.post('http://localhost:5000/users/login', loginDetails)
.then((res) => {
})
.catch((err) => {
})
}
I'm stuck on trying to figure out how to make the data be sent back to react after either of the above functions. In the register function I've sent back the res.data which contains the session. See blow route for express
router.post('/register', async (req, res) => {
//Destructure req.body.
const {username,password} = req.body;
//hash password.
const hashedPassword = await hashPassword(password);
//Create new user to store in mongodb.
const newUser = {
username: username,
password: hashedPassword
}
//Create new user document
await User.create(newUser, (err, newlyAddedUser) => {
if(err){
console.log(err);
}else{
req.session.username = newlyAddedUser.username;
console.log(req.session);
res.send(req.session);
}
})
});
With the console.log(req.session) it outputs the cookie and the username I added in the session itself.
Should I make a user object on the react side and store the username and password inside?
Should I be passing back the session itself to the route with history.push({ pathname: '/',state: res.data});
How can I verify that the session is valid for the user using connect-mongo?
I spent 10 minutes trying to understand what is your goal. Didn't find.
But whatever you need to use a jsonwebtoken if you want to verify that the session is valid like you said
Enjoy https://jwt.io/
https://www.npmjs.com/package/jsonwebtoken
I wouldn't store the session in the History state API like you do.
history.push({
pathname: '/',
state: res.data
});
You better use a sessionStorage and/or localStorage. The name just talks by itself.
Give me one point please
I have a component GigRegister - one of it's functions is to get all the documents from a collection, and return only the documents created by the currently logged in user:
authListener() {
auth().onAuthStateChanged(user => {
if(user){
this.setState({
userDetails:user
},
() =>
firebase.firestore().collection('gig-listing').onSnapshot(querySnapshot => {
let filteredGigs = querySnapshot.docs.filter(snapshot => {
return snapshot.data().user === this.state.userDetails.uid
})
this.setState({
filterGigs: filteredGigs
})
})
) //end of set state
} else {
this.setState({
userDetails:null
})
console.log('no user signed in')
}
})
}
componentDidMount() {
this.authListener();
}
Another function of this component is to capture data from the user and then post it to firebase, after which it redirects to another component.
handleSubmit(e) {
let user = auth().currentUser.uid;
const gigData = {
name: this.state.name,
venue: this.state.venue,
time: this.state.time,
date: this.state.date,
genre: this.state.genre,
tickets: this.state.tickets,
price: this.state.price,
venueWebsite: this.state.venueWebsite,
bandWebsite: this.state.bandWebsite,
user: user
};
auth()
.currentUser.getIdToken()
.then(function (token) {
axios(
"https://us-central1-gig-fort.cloudfunctions.net/api/createGigListing",
{
method: "POST",
headers: {
"content-type": "application/json",
Authorization: "Bearer " + token,
},
data: gigData,
}
);
})
.then((res) => {
this.props.history.push("/Homepage");
})
.catch((err) => {
console.error(err);
});
}
So here's the issue. Sometimes this component works as it should, and the data submit and redirect work as intended. Occasionally though, I'll hit submit but trigger the message TypeError: Cannot read property 'uid' of null . Interestingly, the post request is still made.
I've been logged in both when it succeeds and fails, and I can only assume that this.state.userDetails.uid evaluating to null means that auth state has expired, or that the component is rendering before userDetails can be assigned a value?
The issue I have is that I can't tell if this is an async problem, or an auth state persistence problem, and I also can't figure why it's a sporadic failure.
This line of code might be causing you trouble:
let user = auth().currentUser.uid;
currentUser will be null if there is no user signed in at the time it was accessed (or it's not known for sure if that is the case). This is covered in the API documentation.
Ideally, you should never use currentUser, and instead rely on the state provided by onAuthStateChanged. I talk about this in detail in this blog post. If you do need to use currentUser, you should check it for null before referencing properties on it.
You should also know that getting an ID token is best done by using a listener as well. The call is onIdTokenChanged, and it works like the auth state listener.
Keep in mind also that setState is asynchronous and doesn't set the state immediately. It's possible that your Firestore query isn't getting the state it needs immediately.
I am fetching a profile object from my API following user authentication. The fetch returns the profile object as expected, however my server logger clearly shows a profile object containing an "id" and "username", but the initial object returned to the client has only the "username". I am only able to access the "id" property of the profile abject after I refresh.
Not sure how to fix this, but ive tried everything I can think of...
Login Form
export default class LoginForm extends Component {
static defaultProps = {
onLoginSuccess: () => { }
}
state = { error: null }
handleSubmitJwtAuth = ev => {
ev.preventDefault()
this.setState({ error: null })
const { username, password } = ev.target
//login request
AuthApiService.postLogin({
username: username.value,
password: password.value,
})
//login response
.then(res => {
//updates context profile with username value after login
this.props.updater({ username: username.value })
username.value = ''
password.value = ''
TokenService.saveAuthToken(res.authToken)
this.props.onLoginSuccess()
})
.catch(res => {
this.setState({ error: res.error })
})
}
Profile API Service
const ProfileApiService = {
getProfile() {
return fetch(`${config.API_ENDPOINT}/profile`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `bearer ${TokenService.getAuthToken()}`
}
})
.then(res => {
return (!res.ok)
? res.json().then(e => Promise.reject(e))
: res.json()
}
);
}
}
(API) Profile Service
const ProfileService = {
getProfile : (db,id) =>{
return db
.from('v_users')
.select('id','username')
.where({id})
.first();
},
serializeProfile(profile){
return {
id: profile.id,
username: xss(profile.username)
};
}
}
initially, console.log(this.state.profile.id) //undefined
after a refresh, console.log(this.state.profile.id) // 7
the server log shows this object being returned initially
{ id: 7, username: 'qber83' }, however as mentioned above, I am unable to access the "id" property without refreshing the browser.
The problem here could be that your state is not updated properly, since the object returned is right the API services work, so here your context updater or this.props.onLoginSuccess() might contain the issue.
I have a React application that is registered in Azure Active Directory. In the API Permissions section, I have added permissions to access the API I am trying to access.
I am using the react-adal package to handle login and storage of access tokens when the user enters the app. My understanding is that the access token for the API is created at this point and adalFetch handles the logistics during the call to the API.
The response from the API is an error object (I replaced the actual id's; yes they match exactly and are correct in AAD):
{
message: "AADSTS500011: The resource principal named https://<domain>.onmicrosoft.com/APP_ID/access_as_user was not found in the tenant named TENANT. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant."
msg: "invalid_resource"
}
I have searched high and low to find a solution to why this isn't working. There is documentation on the API, but none specifying a resource or anything beyond the various endpoints i.e. http://thing-api.azurewebsites.net/api/endpointGoesHere
The API page states:
To use the API, apps need to implement modern authentication (OIDC) using AzureAD (AAD) and then request a token from AAD for the API.
The app id in Azure is https://domain.onmicrosoft.com/APP_ID and requires the “access_as_user” scope.
adalConfig.js
import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';
export const adalConfig = {
clientId: CLIENT_ID,
tenant: TENANT,
endpoints: {
thingApi: 'https://<domain>.onmicrosoft.com/APP_ID/access_as_user',
graphApi: 'https://graph.microsoft.com',
},
cacheLocation: 'localStorage',
};
export const authContext = new AuthenticationContext(adalConfig);
export const adalApiFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.thingApi, fetch, url, options);
export const adalGraphFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.graphApi, fetch, url, options);
Function for the API call. Executed in componentDidMount.
TrainLanding.jsx
//Returns error
fetchData = () => {
adalApiFetch(fetch, 'http://thing-api.azurewebsites.net/api/EventGet', {})
.then((response) => {
response.json()
.then((responseJson) => {
this.setState({ apiResponse: JSON.stringify(responseJson, null, 2) }, () => {
console.log(this.state.apiResponse)
})
});
})
.catch((error) => {
console.error(error);
})
}
//works perfectly fine
fetchGraph = () => {
adalGraphFetch(fetch, 'https://graph.microsoft.com/v1.0/me', {})
.then((response) => {
response.json()
.then((responseJson) => {
this.setState({ apiResponse: JSON.stringify(responseJson, null, 2) }, () => {
console.log(this.state.apiResponse)
})
});
})
.catch((error) => {
console.error(error);
})
}
I set up a graph API call in the exact same way to test the method, and it works perfectly fine. So I know adal is set up correctly, I just don't understand the error and where I am going wrong. My googling has not yielded any useful results.
Ok, so if you're here, some things to note:
Don't use ADAL. Use MSAL. ADAL is v1 and does not work. Read here for examples: https://www.npmjs.com/package/react-aad-msal
You should wrap your entire app inside the component you get from above. I will show how I did it below.
You must have already registered your app in Azure Active Directory, configured redirect URLs, and included API permissions.
index.js
import { AzureAD, MsalAuthProviderFactory, LoginType } from 'react-aad-msal';
import { msalConfig, authParams } from './msalConfig';
class Index extends Component {
state = {
userInfo: null,
}
userJustLoggedIn = (accInfo) => {
this.setState({
userInfo: accInfo
})
}
render() {
return(
<AzureAD
provider={
new MsalAuthProviderFactory(msalConfig, authParams, LoginType.Redirect)
}
forceLogin={true}
accountInfoCallback={this.userJustLoggedIn}
>
<HashRouter>
<App userInfo={this.state.userInfo}/>
</HashRouter>
</AzureAD>
);
}
}
ReactDOM.render(
<Index/>, document.getElementById('root')
);
This might not be what your index looks like if you are using the most recent version of Create React App. I converted the Index into a component for a couple of reasons. First, the authentication loop for me was getting stuck 1 refresh short when redirecting. Second, so I could store the logged in user's info in state, update with setState (which forces another render), and then pass it as a prop to the rest of my app.
msalConfig.js
export const msalConfig = {
auth: {
authority: process.env.REACT_APP_AUTHORITY, //this should be "https://login.microsoftonline.com/<your-tenant-id>"
clientId: process.env.REACT_APP_CLIENT_ID, //just "<your-client-id>"
redirectUri: process.env.REACT_APP_REDIRECT //"<url of your app or localhost port you dev on>"
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
export const authParams = {
//can be whatever api scopes you need here **as long as they are from the same API address**
scopes: [
'https://graph.microsoft.com/User.ReadBasic.All',
'https://graph.microsoft.com/email',
'https://graph.microsoft.com/profile',
'https://graph.microsoft.com/User.Read'
],
extraScopesToConsent: [
//any non Microsoft Graph API scopes go here for this example
'any extra strings of APIs to consent to'
]
}
Read above env files and variables here: https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#what-other-env-files-can-be-used
I have a .env.development and a .env.production with the proper redirect URLs for each.
After you have authenticated the user, you can access the API.
You need to acquire a token silently before each API call and use the token in the request. For me it looks like this:
const authProvider = new MsalAuthProviderFactory(msalConfig, authParams);
console.log(authProvider)
authProvider.getAuthProvider().UserAgentApplication.acquireTokenSilent(authParams)
.then((res) => {
axios({
headers: {
'Authorization': 'Bearer ' + res.accessToken
},
method: 'GET',
url: "api address"
})
.then((response) => {
//do stuff with response
console.log(response)
})
.catch((error) => {
console.log('axios fail: ' + error)
})
})
.catch((error) => {
console.log('token fail: ' + error)
})
I put this into a function and called during componentDidMount.
I will update if anything changes. I hope this helps someone.