How to refresh Firebase IdToken after it expires? - reactjs

I'm using express as my backend and I'm using the Firebase Admin SDK to send back the token to the client.
At the moment the token is expired after 1 Hour. I read on the firebase that there isn't any way to change the expiration property because the way it works - the user will get a refreshed token every hour. Is that correct? If so, how I supposed to implement it?
Here is my login route:
exports.login = async (req, res) => {
const user = {
email: req.body.email,
password: req.body.password,
}
// Validate Data
const { valid, errors } = validateLogin(user)
if (!valid) return res.status(400).json(errors)
try {
const data = await firebase
.auth()
.signInWithEmailAndPassword(user.email, user.password)
const token = await data.user.getIdToken()
const cookieOptions = {
httpOnly: true,
secure: false,
}
res.cookie('jwt', token, cookieOptions)
return res.status(201).json({ token })
} catch (err) {
errors.general = 'Wrong credentials, please try again'
return res.status(403).json(errors)
}
}

There is nothing to do on the backend to implement token refreshing. The client app will do that automatically using the Firebase Auth SDK.
Anyway, backend apps aren't supposed to sign in the user - that won't work out the way you expect. When using Firebase Auth, client apps are supposed to sign in using the client SDK. The client app signs in, gets a token, then passes that token to the backend to be verified using the Firebase Admin SDK, then acted on.

Related

Making authFetch from react-token-auth doesn't use access token

I'm building a webapp using react and flask. I'm currently implementing user login features using react-token-auth and flask_praetorian. I've already created my back-end functions that handle logging in and can successfully return a token. However, I am now having issues with making an authenticated request.
My flask function
#app_login.route('/get_username')
#flask_praetorian.auth_required
def protected():
response = jsonify({'username': flask_praetorian.current_user().username})
return response
and on react
const fetchUsername = () => { authFetch(`http://${configData.LOCAL_SERVER}:${configData.WEBAPP_PORT}/get_username`).then(response => {
return response.json()
}).then(response => {
console.log(response)
})
}
I'm using the default createAuthProvider as shown on the react-token-auth project page
export const { useAuth, authFetch, login, logout } = createAuthProvider({
getAccessToken: session => session.accessToken,
storage: localStorage,
onUpdateToken: token =>
fetch(`http://${configData.LOCAL_SERVER}:${configData.WEBAPP_PORT}/app_login/refresh`, {
method: 'POST',
body: token.refreshToken,
}).then(r => r.json()),
});
Whenever I make a request, I get a 401 (UNAUTHORIZED) and react returns the error 'authFetch' was called without access token. Probably storage has no session or session were expired
I've checked my local storage and I can see that the key is there:
Storage {persist:root: '{"player":"{\\"value\\":{\\"name\\":\\"\\",\\"access_toke…_persist":"{\\"version\\":-1,\\"rehydrated\\":true}"}', REACT_TOKEN_AUTH_KEY: '"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2…5OX0.rTBCD7YPD8wrB95v1j9oazNLusKOPErI5jed_XWXDhU"', length: 2}
I'm really trying to avoid using technologies like Redux so any assistance regarding this specific setup would be great.

How to bypass NextAuth when given an external access token

This is not a common use case and even less a good practice, but for the purposes of my project, if an access token is passed as a parameter in the url (e.g.http://localhost:3000?accessToken=myAccessToken), I need to use it in my API calls and "disable" authentication with next auth.
The authentication process is just a fallback in case an accessToken is not passed.
My current implementation is:
storing the accessToken in a cookie in _app.tsx, before the auth
kicks and redirects to the login page :
_app.tsx
...
// Retrieving the callbackURL query params.
const { callbackUrl } = router.query;
// Retrieving the accessToken from the callbackURL.
const params = new URL(callbackUrl as string, 'https://example').searchParams;
const accessToken = params.get('accessToken');
// Storing it in a cookie.
if (storeNumber) {
document.cookie = `storeNumber=${storeNumber}`;
}
...
in my _middleware.ts file, trying to get this cookie, and authorize
the login if the token is present.
_middleware.ts :
export default withAuth({
pages: {
signIn: '/auth/signin',
},
callbacks: {
authorized: ({ req, token }) => {
const accessToken = getCookie('accessToken'); // => null
return !!accessToken;
},
},
});
I'm not even sure I can access the cookie from the _middleware.ts file, or if it's the right way to do this.
Any help would really be appreciated. Thank you guys.
If anyone wants the solution (doubt it), I managed to retrieve the cookie in the middleware like this :
callbacks: {
authorized: ({ req }) => {
const cookie = req.headers.get('cookie');
const accessToken = cookie.split('accessToken=')[1].split(';')[0];
console.log(accessToken);
// Do your logic
return !!accessToken
},
},

Django backend authentication with NextJS frontend form - best practices

I have an API hub that I've built in Django and a frontend end application I've built in NextJS. I'm currently working on authenticating to the Django API in Nextjs and I'm curious about best practices.
Currently, the NextJS app posts the users username/password to an endpoint. This endpoint either returns the users token or the error illustrating the issue.
React
const login = async () => {
let token = await axios.post('/api/accounts/', {
email: email,
password: password
}).then(r => r.data.token).catch(function (error) { console.log(error) })
if (token) {
router.push({
pathname: '/home/',
query: { token: token },
})
}
}
nexjs server api/accounts
export default async (req, res) => {
if (req.method === 'POST') {
try {
// retrieve payment intent data
const {data} = await axios.post('https://website/api/api-token-auth/', req.body)
res.status(200).send(data)
} catch (err) {
res.status(500).json({ statusCode: 500, message: err.message })
}
} else {
res.setHeader('Allow', 'POST')
res.status(405).end('Method Not Allowed')
}
}
Django API
#csrf_exempt
#api_view(["POST"])
#permission_classes((AllowAny,))
def obtain_auth_token(request):
email = request.data.get("email")
password = request.data.get("password")
if email is None or password is None:
return Response({'error': 'Please provide both email and password'},
status=HTTP_400_BAD_REQUEST)
user = authenticate(email=email, password=password)
if not user:
return Response({'error': 'Invalid Credentials'},
status=HTTP_404_NOT_FOUND)
token, _ = Token.objects.get_or_create(user=user)
return Response({'token': token.key},
status=HTTP_200_OK)
Once I receive the token I push the user to the homepage.
My questions are:
Is how I'm authenticating users a good way to do this? Am I overlooking something? This is the first time I've attempted to authenticate to something I've built so I want to get this right.
How should I store this token? What is "best practice" when it comes to authentication creds? I've thought about passing the token around to every component that needs it. I've also peeked at using LocalStorage but again am unsure what most people do in these situations.
Any help you all can provide would be much appreciated!
Thanks in advance!

Handling Sessions with Httponly Cookies in React

What's the best practice handling user session when you get your token from HttpOnly cookies in react?
My login endpoint looks like this and as you can see token is set on cookies:
#Post('login')
#HttpCode(HttpStatus.OK)
async login(#Ip() ipAddress, #Request() req, #Res() res: Response) {
const auth = await this.basicAuthService.login(req.user, ipAddress);
const cookieOptions = setTokenCookie();
res.cookie('token', auth.token, { httpOnly: true });
res.cookie('refreshToken', auth.refreshToken, { httpOnly: true });
res.send(auth);
}
And also I have another endpoint which decodes a token in order to get user Data
#Get('user-data')
async getTokenPayload(#Request() req) {
if (!('token' in req.cookies)) {
throw new HttpException('Token was not provided', HttpStatus.NOT_FOUND);
}
const { token } = req.cookies;
return this.basicAuthService.getTokenPayload(token);
}
On FrontEnd I'm using API Context from React like this, and as you can see I'm fetching data from the /user-data endpoint:
export const UserContext = createContext<UserContextState>(userContextValue);
export const UserProvider:FC<UserProviderProps> = ({ children }) => {
const [user, setUser] = useState<User>(userInitialValue);
useEffect(() => {
const getData = async () => {
const tokenDecoded = await getUserData();
setUser(tokenDecoded.user);
};
getData();
}, []);
return (
<UserContext.Provider value={{ user, setUser }}>
{ children }
</UserContext.Provider>
);
};
It's working ok, the problem is a request is made every time the browser refreshes in order to get the users data and set it on the react state. I'm not sure whether this is a good practice, since sometimes user is not authenticated and obviously that /user-data request returns an error. I don't want to store the token on localStorage or set HttpOnly as false. Is there a better way to do it?
From what I understand is your having server side session lets say for example express-session that which I know of and can explain but I believe that concept is the same with others.
So from what I understand is if when the user is logged in and a session is made that cookie is to be set in browser and will only be removed only if the expiration date has been met besides that then that cookie will stay there. Meaning that even on page reload that cookie will never go anywhere.
So I am to highly believe from what you saying that the cookie is not getting set in browser or maybe you just mis-explained, cause if the cookie is getting set and not yet expired even on page reload should be there
So if you are using NodeJS as your back-end below is an implementation on how you can handle express-session with react app and getting that cookie set in browser once user logged in and saving that session in mongodb the instance a session is made
Firstly you will need the following packages
npm i express-session connect-mongodb-session or yarn add express-session connect-mongodb-session
Now that we have packages that we need to setup our mongoStore and express-session middleware:
//Code in server.js/index.js (Depending on your server entry point)
import expressSession from "express-session";
import MongoDBStore from "connect-mongodb-session";
import cors from "cors";
const mongoStore = MongoDBStore(expressSession);
const store = new mongoStore({
collection: "userSessions",
uri: process.env.mongoURI,
expires: 1000,
});
app.use(
expressSession({
name: "SESS_NAME",
secret: "SESS_SECRET",
store: store,
saveUninitialized: false,
resave: false,
cookie: {
sameSite: false,
secure: process.env.NODE_ENV === "production",
maxAge: 1000,
httpOnly: true,
},
})
);
Now the session middleware is ready but now you have to setup cors to accept your ReactApp so to pass down the cookie and have it set in there by server
//Still you index.js/server.js (Server entry point)
app.use(
cors({
origin: "http://localhost:3000",
methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
credentials: true,
})
);
Now our middlewares are all setup now lets look at your login route
router.post('/api/login', (req, res)=>{
//Do all your logic and now below is how you would send down the cooki
//Note that "user" is the retrieved user when you were validating in logic
// So now you want to add user info to cookie so to validate in future
const sessionUser = {
id: user._id,
username: user.username,
email: user.email,
};
//Saving the info req session and this will automatically save in your mongoDB as configured up in sever.js(Server entry point)
request.session.user = sessionUser;
//Now we send down the session cookie to client
response.send(request.session.sessionID);
})
Now our server is ready but now we have to fix how we make request in client so that this flow can work 100%:
Code below: React App/ whatever fron-tend that your using where you handling logging in
//So you will have all your form logic and validation and below
//You will have a function that will send request to server
const login = () => {
const data = new FormData();
data.append("username", username);
data.append("password", password);
axios.post("http://localhost:5000/api/user-login", data, {
withCredentials: true, // Now this is was the missing piece in the client side
});
};
Now with all this you have now server sessions cookies as httpOnly

Convert Google OAuth id token into Firebase ID token in React

I am using Firebase Auth for login and sign up. I am trying to integrate google sign in. Now I have gotten a google Oauth ID that I need to convert into a Firebase ID token so that I can sign in using the authroization header.
const FirebaseIdToken = `Bearer ${token}`;
localStorage.setItem("FirebaseIdToken", FirebaseIdToken);
API.defaults.headers.common["Authorization"] = FirebaseIdToken;
I am following this link: https://firebase.google.com/docs/reference/rest/auth/#section-sign-in-with-oauth-credential to get a post request but I am having trouble doing it.
const data = {
postBody: token, //Google Oauth token
};
API.post(
`https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp?key=[API-KEY]`,
{ data }
).then((res) => {
console.log(res);
console.log(res.data);
});
I just get a Post 400 error. The body also asks for a requestUri which I'm not sure what that is.
You need to pass the following data:
const data = {
returnSecureToken: true,
requestUri: 'url-where-your-app-is-served-eg-http://localhost',
postBody:`id_token=${token}&providerId=google.com`,
};

Resources