I'm trying to let users log in and register using google Auth, I'm using Context to let the app knows if the user is signed in or not, Register and Login works fine, but i didn't know how to configure Google login.
Register.jsx (Register Page):
const [username,setUsername] = useState("");
const [email,setEmail] = useState("");
const [password,setPassword] = useState("");
const [repeatPassword, setRepeatPassword] = useState("");
const { dispatch, isFetching } = useContext(Context);
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: "REGISTER_START" });
setError(false);
try {
const res = await axios.post("/register", {
username,
email,
password,
repeatPassword,
});
dispatch({ type: "REGISTER_SUCCESS", payload: res.data });
} catch (err) {
dispatch({ type: "REGISTER_FAILURE" });
setError(true);
}
};
Login.jsx (Login Page):
const userRef = useRef();
const passwordRef = useRef();
const { dispatch, isFetching } = useContext(Context);
const [error, setError] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/login", {
email: userRef.current.value,
password: passwordRef.current.value,
});
dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
} catch (err) {
dispatch({ type: "LOGIN_FAILURE" });
setError(true)
}
};
Context Actions.js
export const LoginStart = (userCredentials) => ({
type: "LOGIN_START",
});
export const LoginSuccess = (user) => ({
type: "LOGIN_SUCCESS",
payload: user,
});
export const LoginFailure = () => ({
type: "LOGIN_FAILURE",
});
// THE SAME FOR REGISTER
Context Reducer.js
const Reducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
isFetching: true,
error: false,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
isFetching: false,
error: false,
};
case "LOGIN_FAILURE":
return {
user: null,
isFetching: false,
error: true,
};
// THE SAME FOR REGISTER
default:
return state;
}
};
export default Reducer;
I tried something like this but it didn't work
const handleGoogleLogin = async (e) => {
window.open("http://localhost:4000/auth/google/callback", "_self");
dispatch({ type: "LOGIN_START" });
try {
const res = await fetch("/login/success", {
method: "GET",
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"Access-Control-Allow-Credentials": true,
},
});
dispatch({ type: "LOGIN_SUCCESS", payload: res.user });
} catch (err) {
dispatch({ type: "LOGIN_FAILURE" });
setError(true)
console.log(err)
}
};
I don't if I have to make new actions just for social media authentication, or if this is the correct way but I'm missing something.
This is Auth.js route:
router.get("/login/success", (req, res) => {
if (req.user) {
res.status(200).json({
error: false,
message: "succesfull",
user: req.user,
});
} else {
res.status(403).json({ error: true, message: "Not Authorized" });
}
});
Related
I'm trying to handle the login of a react app using reducer.
Here is the reducer fuction =>
export const AuthReducer = (initialState, action) => {
switch (action.type) {
case "REQ_LOGIN":
return {
...initialState,
loading: true
};
case "REQ_REGISTER":
return {
...initialState,
loading: true
};
case "LOGIN_OK":
return {
id: action.payload.user,
token: action.payload.token,
rol: action.payload.rol,
loading: false
};
case "LOGIN_FAIL":
return {
...initialState,
loading: false,
errorMessage: action.error
};
case "LOGOUT":
return {
id: undefined,
token: undefined,
loading: false
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
};
and this is the fuction I user to dispatch the login =>
const requestOptions = (sendData) => {
return {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sendData)
}
};
const fetchUserToDatabase = async (urlPetition, reqData) => {
try {
const res = await fetch(`${BASE_URL}/${urlPetition}`, requestOptions(reqData));
const data = await res.json();
return data;
} catch (error) {
console.error(error)
}
};
export const loginUser = async (dispatch, loginData) => {
localStorage.removeItem('currentUser');
try {
dispatch({ type: "REQ_LOGIN" });
const data = await fetchUserToDatabase("users/login", loginData)
if (data.data) {
dispatch({ type: "LOGIN_OK", payload: data.data });
localStorage.setItem('currentUser', JSON.stringify(data.data));
}
} catch (error) {
dispatch({ type: 'LOGIN_FAIL', error: error });
}
};
Then whenever I need to login I call loginUser(dispatch, loginForm), for example.
The problem is that the 401 unauthorized error is not being handled by the try, catch but is being sent in the response as res.status. so I literally don't know how to catch it outside the reducer and the actions function.
Error auth 401
I got this error whenever I try to log In using Google Login API
In my console I get this error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
to the client
and in the screen I get white page with this error:
InternalOAuthError: Failed to fetch user profile
I'm using two Login method, one is normal and one using Passport JS
Login.jsx Login Page
import { useEffect, useContext, useRef } from "react";
import { Context } from "../../context/Context";
import axios from "axios";
import { useState } from "react"
export default function Login() {
const userRef = useRef();
const passwordRef = useRef();
const { dispatch, isFetching } = useContext(Context);
const [error, setError] = useState(false);
// FOR LOGIN
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/login", {
email: userRef.current.value,
password: passwordRef.current.value,
});
dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
} catch (err) {
dispatch({ type: "LOGIN_FAILURE" });
setError(true)
}
};
// FOR GOOGLE LOGIN
useEffect(() => {
fetch(`http://localhost:4000/login/success`, {
method: 'GET',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Credentials': true,
},
})
.then((response) => {
dispatch({ type: "LOGIN_START" });
if (response.status === 200) return response.json();
throw new Error('failed to authenticate user');
})
.then((responseJson) => {
dispatch({ type: "LOGIN_SUCCESS", payload: responseJson.data });
})
.catch((error) => {
dispatch({ type: "LOGIN_FAILURE" });
// eslint-disable-next-line no-console
console.error("Failed to authenticate user", error)
});
}, []);
const google = () => {
window.open("http://localhost:4000/auth/google/callback", "_self");
};
return()
}
auth.js Route:
router.get("/login/success", (req, res) => {
if (req.user) {
res.status(200).json({
error: false,
message: "succesfull",
user: req.user,
cookies: req.cookies
});
} else {
res.status(403).json({ error: true, message: "Not Authorized" });
}
});
I am working on trying to set up JWT authentication using qraphql and redux.
Currently the register function is working and posts the new user to the database, but I cant seem to get the loadUser function to work.
authAction.js
const API_URL = 'https://my-server.com/api';
export const register = ({name, email, password}) => dispatch => {
axios.post(
API_URL, {
query: `mutation {
userSignup( name: "${name}", email: "${email}", password: "${password}"){
name,
email,
password
}
}
`,
})
.then(res =>
dispatch({
type: REGISTER_SUCCESS,
payload: res.data.data
}))
.catch(err => {
dispatch({
type: REGISTER_FAIL
})
})
}
export const loadUser = (email, password) => dispatch => {
dispatch({type: USER_LOADING})
axios.post(
API_URL, {
query: `query {
userLogin(email: "${email}", password: "${password}"){
email,
password,
token
}
}
`,
})
.then(res => dispatch({
type: USER_LOADED,
type: LOGIN_SUCCESS,
payload: res.data.data
}))
.catch(err => {
dispatch(retrunErrors(err.response.data, err.response.status));
dispatch({
type: AUTH_ERROR
});
});
}
Both of these functions should return the token, and the reducer should set the token to localStorage.
authReducer.js
...
case LOGIN_SUCCESS:
case REGISTER_SUCCESS:
localStorage.setItem('token', action.payload.token)
console.log(action.payload)
return {
...state,
...action.payload,
isAuthenticated: true,
isLoading: false,
}
...
I did have the login working using hooks (did not get to configuring the signUp), but it was all in app.js and it really needed to be broken down. I was told that i needed to move everything to redux, so here i am. I have dug through tons of documentation, but cant find the solution.
FWIW, here is the old app.js (its long)
const API_URL = 'https://my-server.com/api';
function App() {
const initialLoginState = {
isLoading: false,
userName: null,
userToken: null,
};
const setUser = (token, user) => {
if (token) {
axios.defaults.headers.common.Authorization = `Bearer ${token}`;
} else {
delete axios.defaults.headers.common.Authorization;
}
return {type: 'LOGIN', id: user.email, token: token};
};
// Set a user after login or using local (AsyncStorage) token
const setUserLocally = async (key, value) => {
// Set token & user object
// const items = [
// ['userToken', token],
// ['user', JSON.stringify(user)],
// ];
await localStorage.setItem(key, value);
};
const unsetUserLocally = () => {
// Remove token
localStorage.removeItem('userToken');
localStorage.removeItem('user');
return {type: 'LOGOUT'};
};
const loginReducer = (prevState, action) => {
switch (action.type) {
default:
return {
...prevState,
userToken: action.token,
isloading: false,
}
case 'RETRIEVE_TOKEN':
return {
...prevState,
userToken: action.token,
isloading: false,
};
case 'LOGIN':
return {
...prevState,
userName: action.id,
userToken: action.token,
isloading: false,
};
case 'LOGOUT':
return {
...prevState,
userName: null,
userToken: null,
isloading: false,
};
case 'REGISTER':
return {
...prevState,
userName: action.id,
userToken: action.token,
isloading: false,
};
}
};
const [loginState, dispatch] = useReducer(loginReducer, initialLoginState);
const auth = {
signIn: async (userName, password) => {
try {
const response = await axios
.post(
API_URL,
query({
operation: 'userLogin',
variables: {
email: userName,
password: password,
},
fields: ['user {name, email, role}', 'token'],
})
);
let message = 'Please try again.';
if (response.data.errors && response.data.errors.length > 0) {
message = response.data.errors[0].message;
} else if (response.data.data.userLogin.token !== '') {
const token = response.data.data.userLogin.token;
const user = response.data.data.userLogin.user;
setUserLocally('userToken', token).then(() => {
return setUserLocally('user', JSON.stringify(user));
});
dispatch(setUser(token, user));
}
} catch (error) {
console.log(error);
}
},
signOut: () => {
dispatch(unsetUserLocally());
},
signUp: () => {
// setUserToken('sdf');
// setIsLoading(false);
},
getCurrentUser: () => {
//return 'test'; //JSON.parse(AsyncStorage.getItem('userToken'));
let userArr = {};
const value = async () => {
await localStorage.multiGet(['user', 'userToken']).then(
(response) => {
response.forEach((item) => {
userArr[item[0]] = item[1];
});
return userArr;
},
);
};
return value;
},
};
console.log(loginState.userToken)
useEffect(() => {
let users = async () => {
let userToken;
try {
userToken = await localStorage.getItem('userToken');
} catch (e) {
console.log(e);
}
dispatch({type: 'RETRIEVE_TOKEN', token: userToken});
};
users();
}, []);
// if (loginState.isLoading === true) {
// return <Loading />;
// }
return (
<AuthContext.Provider value={auth}>
<Router>
<AuthSwitcher user={loginState.userToken}/>
</Router>
</AuthContext.Provider>
)
}
export default App
I'm working on a project of mine using React and Redux.
I'm trying to retrieve the token field from my auth state from my store (which contains an authentication key that I intend to send over to my server side) using useSelectorbut the thing is, that it doesn't load in time. How do I fix this?
I'll add below parts of my code that are associated with this issue:
LikeButton.js: (here is the part where I'm trying to retrieve token)
...
const LikeButton = ({ postId }) => {
const classes = useStyles();
const [isLiked, setIsLiked] = useState(false);
const isMount = useIsMount();
const dispatch = useDispatch();
const { token } = useSelector((state) => state.auth);
const { likedPostsIds } = useSelector((state) => state.likes);
useEffect(() => {
if (token) dispatch(fetchLikedPosts(token));
}, [token]);
...
likeActions.js:
...
export const fetchLikedPosts = ({ token }) => (dispatch) => {
fetch("http://localhost:8080/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: fetchLikedPostsQuery(),
})
.then((res) => res.json())
...
authActions.js
...
export const signIn = ({ password, email }) => (dispatch) => {
dispatch({
type: IS_AUTH_LOADING,
});
fetch("http://localhost:8080/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: signInQuery(password, email),
})
.then((res) => res.json())
.then((resData) => {
if (resData.errors) {
dispatch(showNotification("User authentication has failed!", ERROR));
dispatch({
type: AUTH_ERROR,
});
} else {
setLocalStorageAuth(resData.data.signIn.token, expiryDate);
dispatch({
type: SIGN_IN,
payload: resData.data.signIn,
});
}
})
.catch((err) => {
dispatch(
showNotification(
"User authentication has failed. Please try again later.",
ERROR
)
);
dispatch({
type: AUTH_ERROR,
});
emptyLocalStorage();
});
};
...
auth.js:
...
const initialState = {
token: localStorage.getItem("token"),
isAuth: localStorage.getItem("isAuth"),
expiryDate: localStorage.getItem("expiryDate"),
isLoading: false,
};
export default function authReducer(state = initialState, action) {
switch (action.type) {
case REGISTER_LOADING:
case IS_AUTH_LOADING:
return {
...state,
isLoading: true,
};
case SIGN_IN:
return {
token: action.payload.token,
isAuth: true,
isLoading: false,
};
case AUTH_ERROR:
case LOGIN_FAIL:
case REGISTER_FAIL:
case LOGOUT_SUCCESS:
return {
token: null,
isAuth: false,
isLoading: false,
};
case CREATE_USER:
return {
...state,
isLoading: false,
};
default:
return state;
}
}
...
The fetchLikedPosts function expects an object with a token key as an argument. the effect currently passes just the token as an argument. Try
useEffect(() => {
// note the change from token to {token}
if (token) dispatch(fetchLikedPosts({token}));
}, [token]);
i tried looking for similar answers to help solve my problem but i couldn't find anything using react redux hooks. This code was from a tutorial and originally written using the Context api. I wanted to trying using it with react-redux-hooks, but i got stuck. Basically i'm trying to register a user with a name, email and password, then pass these three as an object to the express server which will validated it and give me back a jwt token. Then come back to the client side and send the token to the reducer, which adds the token to localstorage and sets the state to isAuthenticated. The error i get is on the dispatch.
Dispatch
const onSubmit = e => {
e.preventDefault();
if (name === "" || email === "" || password === "") {
dispatch(setAlert("Please enter all fields", "danger"));
} else if (password !== password2) {
dispatch(setAlert("Passwords do not match", "danger"));
} else {
dispatch(register({ name, email, password })); // Error is here
}
setTimeout(() => {
dispatch(removeAlert());
}, 5000);
};
Action
export const register = async formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
try {
const res = await axios.post("/api/users", formData, config);
return {
type: "REGISTER_SUCCESS",
payload: res.data
};
} catch (err) {
return {
type: "REGISTER_FAIL",
payload: err.response.data.msg
};
}
};
Reducer
const authReducer = (
state = {
token: localStorage.getItem("token"),
isAuthenticated: null,
loading: true,
user: null,
error: null
},
action
) => {
switch (action.type) {
case "REGISTER_SUCCESS":
console.log("register success");
localStorage.setItem("token", action.payload.token);
return {
...state,
...action.payload,
isAuthenticated: true,
loading: false
};
case "REGISTER_FAIL":
console.log("register failed");
localStorage.removeItem("token");
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
user: null,
error: action.payload
};
default:
return state;
}
};
Store
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|| compose;
const store = createStore(
allReducers,
composeEnhancers(applyMiddleware(thunk))
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Express server
router.post(
"/",
[
check("name", "Please a name")
.not()
.isEmpty(),
check("email", "Please include a valid email").isEmail(),
check(
"password",
"Please enter a password with 6 or more characters"
).isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const { name, email, password } = req.body;
try {
let user = await User.findOne({ email });
if (user) {
return res.status(400).json({
msg: "User already exists"
});
}
user = new User({
name,
email,
password
});
// hash passsword
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
config.get("jwtSecret"),
{
expiresIn: 360000
},
(err, token) => {
if (err) throw err;
res.json({
token
});
}
);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
I believe this question has answers to the issue your experiencing here: how to async/await redux-thunk actions?
Using this example, it may look something like this (wasn't able to test it):
export const register = formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
const request = axios.post("/api/users", formData, config);
return dispatch => {
const onSuccess = success => {
dispatch({
type: "REGISTER_SUCCESS",
payload: success.data
});
return success;
};
const onError = error => {
dispatch({
type: "REGISTER_FAIL",
payload: error.response.data.msg
});
return error;
};
request.then(onSuccess, onError);
};
};
export const register = formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
return async dispatch => {
const onSuccess = success => {
dispatch({
type: "REGISTER_SUCCESS",
payload: success.data
});
return success;
};
const onError = error => {
dispatch({
type: "REGISTER_FAIL",
payload: error.response.data.msg
});
return error;
};
try {
const success = await axios.post("/api/users", formData, config);
return onSuccess(success);
} catch (error) {
return onError(error);
}
}
};