I am working on authentication for my react native app.
The problem I am having is that the signInUser function seems to be executing in the wrong order. I want the signIn function to fully execute before moving on.
However, that doesn't seem to be happening as I am getting this response in my console, with the "undefined" coming from console.log(response) in the SignInScreen.
Here is the console:
undefined
16d0707a3508a9b43b8c36c8574ca73d8b4b26af
I have this function in the SignInScreen.js
import { signIn } from "../services/authService";
import { useAuthDispatch } from "../contexts/authContext";
const SignInScreen = ({ navigation }) => {
const dispatch = useAuthDispatch();
const [signInLoading, setSignInLoading] = useState(false);
const signInUser = async (values) => {
const { email, password } = values;
setSignInLoading(true);
signIn(email, password)
.then((response) => {
console.log(response);
dispatch({
type: "SIGN_IN",
token: response,
});
})
.catch((e) => {
console.log(e);
})
.finally(() => setSignInLoading(false));
};
And this is my authService.js:
import axios from "axios";
const signIn = async (email, password) => {
axios
.post("http://127.0.0.1:8000/rest-auth/login/", {
username: email,
email: email,
password: password,
})
.then((response) => {
console.log(response.data.key);
return response.data.key;
})
.catch((error) => {
return error;
});
};
How can I fix this?
Related
I am using firebase for authentication in my Next.js app and also I have an express server that serves a REST API, which has a middleware that uses firebase-admin to verify idToken that is sent from my app, to pass the authenticated routes
Currently
The idToken generated by firebase lasts for one hour and if the client is still on my app and
hits any route that needs idToken and if the idToken is expired then the server just throws an error as unauthenticated, which is pretty good work, but this is not desired, I know my user is in there and just idToken is expired
Question
How do I refresh my idToken of a user if it has expired, without having to do a full refresh in the browser to get new idToken
Some Code
AuthContext.tsx
/* eslint-disable no-unused-vars */
import { useRouter } from 'next/router'
import nookies from 'nookies'
import { createContext, useContext, useEffect, useState } from 'react'
import { axios } from '../config/axios'
import firebase from '../config/firebase'
import { AuthUser } from '../types'
import { BaseUser } from '../types/user'
import { getProvider } from '../utils/oAuthProviders'
type AuthContextType = {
user: AuthUser | null
login: (email: string, password: string) => Promise<any>
signup: (email: string, password: string) => Promise<any>
logout: () => Promise<any>
oAuthLogin: (provider: string) => Promise<any>
}
const AuthContext = createContext<AuthContextType>({} as AuthContextType)
export const useAuth = () => useContext(AuthContext)
const fromPaths = ['/login', '/signup']
const formatUser = (user: BaseUser, idToken: string): AuthUser => {
return {
...user,
idToken,
}
}
export const AuthContextProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<AuthUser | null>(null)
const [loading, setLoading] = useState(true)
const router = useRouter()
console.log(user)
useEffect(() => {
const unsub = firebase.auth().onIdTokenChanged((user) => {
if (user) {
user
.getIdToken()
.then(async (idToken) => {
try {
const userResp = await axios.get('/user/me', {
headers: {
Authorization: `Bearer ${idToken}`,
},
})
nookies.set(undefined, 'idk', idToken, { path: '/' })
const {
data: { userFullDetials },
} = userResp
setUser(formatUser(userFullDetials, idToken))
setLoading(false)
if (fromPaths.includes(router.pathname)) {
router.push('/home')
}
} catch (err) {
console.log(err)
setUser(null)
setLoading(false)
}
})
.catch((err) => {
console.log(err.message)
setUser(null)
setLoading(false)
})
} else {
setLoading(false)
setUser(null)
}
})
return () => unsub()
}, [router])
const login = (email: string, password: string) => {
return firebase.auth().signInWithEmailAndPassword(email, password)
}
const signup = (email: string, password: string) => {
return firebase.auth().createUserWithEmailAndPassword(email, password)
}
const oAuthLogin = (provider: string) => {
return firebase.auth().signInWithPopup(getProvider(provider))
}
const logout = async () => {
setUser(null)
await firebase.auth().signOut()
}
const returnObj = {
user,
login,
signup,
logout,
oAuthLogin,
}
return (
<AuthContext.Provider value={returnObj}>
{loading ? (
<div className="flex items-center justify-center w-full h-screen bg-gray-100">
<h1 className="text-indigo-600 text-8xl">S2Media</h1>
</div>
) : (
children
)}
</AuthContext.Provider>
)
}
// auth.ts
// Auth Middleware in express
import { NextFunction, Request, Response } from 'express'
import fbadmin from 'firebase-admin'
import { DecodedIdToken } from '../types/index'
export default async (req: Request, res: Response, next: NextFunction) => {
const authorization = req.header('Authorization')
if (!authorization || !authorization.startsWith('Bearer')) {
return res.status(401).json({
status: 401,
message: 'authorization denied',
})
}
const idToken = authorization.split(' ')[1]
if (!idToken) {
return res.status(401).json({
status: 401,
message: 'authorization denied',
})
}
try {
const decodedToken = await fbadmin.auth().verifyIdToken(idToken)
req.user = decodedToken as DecodedIdToken
return next()
} catch (err) {
console.log(err.message)
return res.status(401).json({
status: 401,
message: 'authorization denied',
})
}
}
The Firebase SDK does that for you. Whenever you call user.getIdToken() it will return a valid token for sure. If the existing token has expired, it will refresh and return a new token.
You can use onIdTokenChanged()and which will trigger whenever a token is refreshed and store it in your state.
However, I don't see any cons in using getIdToken() method whenever you are making an API request to server. You won't have to deal with IdToken observer and get valid token always.
const makeAPIRequest = async () => {
// get token before making API request
const token = await user.getIdToken()
// pass the token in request headers
}
Right now your code makes a request to server to get user's information whenever a token refreshes and that may be redundant.
I want to add a Clickable “Remember Me” checkbox in my login page that tells the browser to save a cookie so that if you close out the window for the site without signing out, the next time you go back, you will be signed back in automatically.that can save username and password
export const getUser = () => {
const userStr = sessionStorage.getItem("user");
if (userStr) return JSON.parse(userStr);
else return null;
};
export const getToken = () => {
return sessionStorage.getItem("token") || null;
};
export const setUserSession = (token, user) => {
sessionStorage.setItem("token", token);
sessionStorage.setItem("user", JSON.stringify(user));
};
export const removeUserSession = () => {
sessionStorage.removeItem("token");
sessionStorage.removeItem("user");
};
export const handleSuccessfulLogin = async (token, rememberMe) => {
localStorage.setItem("token", token);
localStorage.setItem("rememberme", rememberMe);
};
export const handleLogout = () => {
localStorage.clear();
};
This is my login that work with api
const handelLogin = () => {
setError(null);
setLoading(true);
axios
.post("https://www.mecallapi.com/api/login", {
username: username,
password: password,
})
.then((response) => {
setLoading(false);
setUserSession(response.data.token, response.data.user);
navigate("/Dashboard");
})
.catch((error) => {
setLoading(false);
if (error.response.status === 401 || error.response.status === 400) {
setError(error.response.data.message);
} else {
setError("somthing went wrong ,please try again");
}
});
};
This is my remember me checkbox
<div className="login-bottom">
<Checkbox {...label} />
</div>
I am using firebase for authentication in my Next.js app and also I have an express server that serves a REST API, which has a middleware that uses firebase-admin to verify idToken that is sent from my app, to pass the authenticated routes
Currently
The idToken generated by firebase lasts for one hour and if the client is still on my app and
hits any route that needs idToken and if the idToken is expired then the server just throws an error as unauthenticated, which is pretty good work, but this is not desired, I know my user is in there and just idToken is expired
Question
How do I refresh my idToken of a user if it has expired, without having to do a full refresh in the browser to get new idToken
Some Code
AuthContext.tsx
/* eslint-disable no-unused-vars */
import { useRouter } from 'next/router'
import nookies from 'nookies'
import { createContext, useContext, useEffect, useState } from 'react'
import { axios } from '../config/axios'
import firebase from '../config/firebase'
import { AuthUser } from '../types'
import { BaseUser } from '../types/user'
import { getProvider } from '../utils/oAuthProviders'
type AuthContextType = {
user: AuthUser | null
login: (email: string, password: string) => Promise<any>
signup: (email: string, password: string) => Promise<any>
logout: () => Promise<any>
oAuthLogin: (provider: string) => Promise<any>
}
const AuthContext = createContext<AuthContextType>({} as AuthContextType)
export const useAuth = () => useContext(AuthContext)
const fromPaths = ['/login', '/signup']
const formatUser = (user: BaseUser, idToken: string): AuthUser => {
return {
...user,
idToken,
}
}
export const AuthContextProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<AuthUser | null>(null)
const [loading, setLoading] = useState(true)
const router = useRouter()
console.log(user)
useEffect(() => {
const unsub = firebase.auth().onIdTokenChanged((user) => {
if (user) {
user
.getIdToken()
.then(async (idToken) => {
try {
const userResp = await axios.get('/user/me', {
headers: {
Authorization: `Bearer ${idToken}`,
},
})
nookies.set(undefined, 'idk', idToken, { path: '/' })
const {
data: { userFullDetials },
} = userResp
setUser(formatUser(userFullDetials, idToken))
setLoading(false)
if (fromPaths.includes(router.pathname)) {
router.push('/home')
}
} catch (err) {
console.log(err)
setUser(null)
setLoading(false)
}
})
.catch((err) => {
console.log(err.message)
setUser(null)
setLoading(false)
})
} else {
setLoading(false)
setUser(null)
}
})
return () => unsub()
}, [router])
const login = (email: string, password: string) => {
return firebase.auth().signInWithEmailAndPassword(email, password)
}
const signup = (email: string, password: string) => {
return firebase.auth().createUserWithEmailAndPassword(email, password)
}
const oAuthLogin = (provider: string) => {
return firebase.auth().signInWithPopup(getProvider(provider))
}
const logout = async () => {
setUser(null)
await firebase.auth().signOut()
}
const returnObj = {
user,
login,
signup,
logout,
oAuthLogin,
}
return (
<AuthContext.Provider value={returnObj}>
{loading ? (
<div className="flex items-center justify-center w-full h-screen bg-gray-100">
<h1 className="text-indigo-600 text-8xl">S2Media</h1>
</div>
) : (
children
)}
</AuthContext.Provider>
)
}
// auth.ts
// Auth Middleware in express
import { NextFunction, Request, Response } from 'express'
import fbadmin from 'firebase-admin'
import { DecodedIdToken } from '../types/index'
export default async (req: Request, res: Response, next: NextFunction) => {
const authorization = req.header('Authorization')
if (!authorization || !authorization.startsWith('Bearer')) {
return res.status(401).json({
status: 401,
message: 'authorization denied',
})
}
const idToken = authorization.split(' ')[1]
if (!idToken) {
return res.status(401).json({
status: 401,
message: 'authorization denied',
})
}
try {
const decodedToken = await fbadmin.auth().verifyIdToken(idToken)
req.user = decodedToken as DecodedIdToken
return next()
} catch (err) {
console.log(err.message)
return res.status(401).json({
status: 401,
message: 'authorization denied',
})
}
}
The Firebase SDK does that for you. Whenever you call user.getIdToken() it will return a valid token for sure. If the existing token has expired, it will refresh and return a new token.
You can use onIdTokenChanged()and which will trigger whenever a token is refreshed and store it in your state.
However, I don't see any cons in using getIdToken() method whenever you are making an API request to server. You won't have to deal with IdToken observer and get valid token always.
const makeAPIRequest = async () => {
// get token before making API request
const token = await user.getIdToken()
// pass the token in request headers
}
Right now your code makes a request to server to get user's information whenever a token refreshes and that may be redundant.
Uncaught (in promise) Error: Request failed with status code 400
I need to make a page request to the database for logging into the system, but I'm already too confused and don't know how to remove this error.
Before that there was the error "Actions must be plain objects. Use custom middleware for async actions."
After that I connected Redux-Thunk and the current error appeared.
Actions
export const auth = (email, password, isLogin) => {
return async(dispatch) => {
dispatch(authData())
let url = 'https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=AIzaSyAU8gNE0fGG8z9zqUyh68Inw9_RzljhCCs'
if (isLogin) {
url = 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=AIzaSyAU8gNE0fGG8z9zqUyh68Inw9_RzljhCCs'
}
const response = await axios.post(url, authData)
console.log(response.data)
}
}
const authData = (email, password, returnSecureToken = true) => ({
type: 'LOGIN',
email,
password,
returnSecureToken
})
Component
loginHandler = () => {
this.props.auth(
this.props.AuthMail,
this.props.AuthPass,
true
)
}
registerHandler = () => {
this.props.auth(
this.props.AuthRegMail,
this.props.AuthRegPass,
false
)
}
const mapDispatchToProps = dispatch => {
return {
auth: (email, password, isLogin) => dispatch(auth(email, password, isLogin))
}
}
// You forgot to add the arguments to authData function
dispatch(authData())
// Here you are passing in a function as the second argument
const response = await axios.post(url, authData)
Should probably be something like this:
export const auth = (email, password, isLogin) => {
return async (dispatch) => {
const url = isLogin ? 'example.com/login' : 'example.com/signup';
const response = await axios.post(url, {
email,
password,
returnSecureToken: true,
});
console.log(response.data);
// Handle this action somewhere to store the signed in user data in redux
dispatch({
type: "LOGIN",
payload: response.data
})
}
}
I have an action that currently works fine using .then but when I try and convert it to async ... await it suddenly can't access the first parameter of the function. The second parameter still works fine though.
Current function that works fine:
export const signInUser = (email, password) => {
return (dispatch) => {
return firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(() => {
console.log('signed in')
const { uid, email } = firebase.auth().currentUser
dispatch({ type: 'SIGN_IN', uid, email })
return dispatch(fetchAllData())
})
.catch(error => {
throw (error)
})
}
};
New function that doesn't work because 'email' is undefined.
export const signInUser = (email, password) => {
return async (dispatch) => {
console.log('testing')
console.log(password)
console.log('testing', email, password)
await firebase.auth().signInWithEmailAndPassword(email, password)
console.log('signed in')
const { uid, email } = firebase.auth().currentUser
dispatch({ type: 'SIGN_IN', uid, email })
return dispatch(fetchAllData())
}
};
The first console log of console.log('testing') works fine and outputs the string 'testing'. The second console log of console.log(password) also works fine and prints the inputted password. But the third console log console.log('testing', email, password) doesn't get shown at all.
This is how the function is called:
Parent component passes it to child component:
submit={(email, password) => dispatch(signInUser(email, password))}
Child component calls it:
const submitForm = (event) => {
event.preventDefault();
if (validForm) {
setLoading(true)
submit(email, password)
.catch(err => {
setLoading(false)
console.log('catch:', err.code)
setError(err.code)
})
}
}
The output I am receiving is catch: undefined.
Also, if I change the function to this:
export const signInUser = (email, password) => {
const userEmail = email
return async (dispatch) => {
console.log('testing')
console.log(password)
console.log('testing', userEmail, password)
await firebase.auth().signInWithEmailAndPassword(userEmail, password)
console.log('signed in')
const { uid, email } = firebase.auth().currentUser
dispatch({ type: 'SIGN_IN', uid, email })
return dispatch(fetchAllData())
}
};
Then it works fine. But I don't know why I would need to change it like this?
Thanks
Ok so the reason it wasn't working was because on the next line I was declaring email like this const { uid, email } = firebase.auth().currentUser
which was overriding the function parameter of email and therefore giving me the error that i was using email before it was declared.