Accessing id token of firebase 9 in axios interceptor directly - javascript

Is there a way to get the id token from firebase 9 directly in the axios interceptor? It was possible with firebase 8.
import axios from "axios";
import config from "../config";
import { getAuth, getIdToken } from "firebase/auth";
const API = axios.create({
responseType: "json",
baseURL: config.ApiUrl
});
API.interceptors.request.use(async (request) => {
const auth = getAuth();
const { currentUser } = auth;
request.headers = {
Authorization: `Bearer ${await currentUser.getIdToken()}`,
};
return request;
});
currentUser is null first because it is loaded async by firebase. How can I access it directly without always having the problem that the first time it crashes because the user is not loaded yet?
Thank your for your help.

You can create a function that waits for onAuthStateChanged() to load auth state and returns a promise containing user's token. Try:
const getUserToken = async () => {
return new Promise((resolve, reject) => {
const unsub = onAuthStateChanged(getAuth(), async (user) => {
if (user) {
const token = await getIdToken(user);
resolve(token)
} else {
console.log("User not logged in")
resolve(null)
}
unsub();
});
})
}
API.interceptors.request.use(async (request) => {
const token = await getUserToken();
if (token) {
request.headers = {
Authorization: `Bearer ${token}`,
};
} else {
// prompt user to login?
}
return request;
});
Make sure you have initialized Firebase SDK before using getAuth(). I recommend creating a different file firebase.js, initialize required services and exporting the instances as explained in this answer.

Related

How can I persist auth state in a nodejs app

So, I am learning NodeJs by creating this backend that fetches some data from a third-party API, the API requires auth. I couldn't figure out how to avoid sending an auth request to the third-party API whenever I wanted to fetch data from it. is there any way I could store the auth state in the app?
const axios = require("axios");
const AUTH_URL = process.env.AUTH_URL;
const REPORT_BASE_URL = process.env.REPORT_BASE_URL;
const X_API_KEY = process.env.X_API_KEY;
const getCompanies = async (req, res) => {
let idToken;
// auth
const authPayload = JSON.stringify({
// ...
});
const config = {
method: "post",
// ...
};
try {
const { data } = await axios(config);
idToken = data.idToken; // set idToken necessary for fetching companies
} catch (error) {
console.log(error);
}
// get company by full text query
const { full_text_query } = req.query;
if (!full_text_query)
return res.send("No full_text_query parameter provided");
try {
const { data } = await axios.get(
`${REPORT_BASE_URL}/companies?full_text_query=${full_text_query}`,
{
headers: {
"x-api-key": X_API_KEY,
Accept: "application/json",
authorization: idToken,
},
}
);
res.status(200).json(data);
} catch (error) {
console.log(error);
}
};
module.exports = {
getCompanies,
};
You can break out a function like fetchIdToken and store a Promise that resolves with the idToken in memory.
let idTokenPromise;
async function fetchIdToken () {
if (idTokenPromise) return idTokenPromise;
return idTokenPromise = new Promise(async (resolve) => {
...
resolve(data.idToken);
})
}
You can then use await fetchIdToken() at the start of getCompanies.
You can also just store the idToken in memory. This is slightly simpler, but does mean that you can have a race-condition when multiple getCompanies requests happen at the same time:
let idToken;
async function fetchIdToken () {
if (idToken) return idToken;
...
idToken = data.idToken;
return idToken;
}

Django + React Axios instance header conflict?

I have all my functions based views on django protected with #permission_classes([IsAuthenticated]) so I have to send a JWT as Bearer token on every request.
In the first version I was using this code:
import axios from 'axios';
import { decodeUserJWT } from '../../extras'
const user = JSON.parse(localStorage.getItem("user"));
var decoded = decodeUserJWT(user.access);
var user_id = decoded.user_id
const instance = axios.create({
baseURL: 'http://localhost:8000/api',
headers: {Authorization: 'Bearer ' + user.access},
params: {userAuth: user_id}
});
export default instance;
Everything was working fine.
But then I added interceptors so I could handle the refreshToken process:
const setup = (store) => {
axiosInstance.interceptors.request.use(
(config) => {
const token = TokenService.getLocalAccessToken();
if (token) {
// const uid = await decodeUserJWT(token);
config.headers["Authorization"] = 'Bearer ' + token;
// config.headers["userAuth"] = uid;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
const { dispatch } = store;
axiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const originalConfig = err.config;
if (originalConfig.url !== "/auth/token/obtain/" && err.response) {
console.log("TOKEN INTERCEPTOR");
// Access Token was expired
if (err.response.status === 401 && !originalConfig._retry) {
originalConfig._retry = true;
try {
const rs = await axiosInstance.post("/auth/token/refresh/", {
refresh: TokenService.getLocalRefreshToken(),
});
const { access } = rs.data;
dispatch(refreshToken(access));
TokenService.updateLocalAccessToken(access);
return axiosInstance(originalConfig);
} catch (_error) {
return Promise.reject(_error);
}
}
}
return Promise.reject(err);
}
);
};
What happens?
When I add the line config.headers["userAuth"] = uid; the django server console starts showing up that when the react app tries to access the routes it gets a Not Authorized, and when I take that line off de code ... it works fine.
I also tried to pass the param userAuth in the axios.create and keep only the Bearer config inside the interpector code, but still no positive result, the code with the interpector code only works when I take off the userAuth line from axios.
Any ideia on why this is happening and how can I fix this?

error retrieving user Id token assigned by firebase in the client side

I am using JWT based authentication using firebase Admin SDK in express js.
according to the sign in with custom token when we sign the user with the function signInWithCustomToken(token) firebase sets a user-id token for that user.
according to retrieve id tokens
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
we can get the token if the user is logged in
but executing this I get error that getIdToken value is null.
i changed the code to
const getUser = async () => {
const token = await firebase.auth().currentUser.getIdToken(/* forceRefresh */true).catch(function(error) {
console.log(error)
});
const userToken = await token;
const getData = async (userToken) => {
const response = await fetch('/getUser', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({idToken: userToke})
})
const data = await response.json()
console.log(responnse)
}
}
getUser();
but still receiving the same error
I looked up for some solutions and found similar answers to the question one of which I implemented was solution
it used onAuthStateChanged method and I am using
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-auth.js"></script>
in cdn but now am getting
Uncaught (in promise) TypeError: firebase.auth.onAuthStateChanged is not a function
at profile:40
at new Promise (<anonymous>)
at getIdTokenRefreshed (profile:37)
at profile:50
I changed the above code to this
firebase.initializeApp(firebaseConfig);
const getIdTokenRefreshed = async () => {
return new Promise(async (resolve, reject) => {
const unsubscribe = await firebase
.auth
.onAuthStateChanged(async user => {
unsubscribe()
const refreshedToken = await user
.getIdToken(true)
.catch(err => console.error(err))
resolve(refreshedToken)
console.log(refreshedToken)
}, reject)
});
}
getIdTokenRefreshed();
still getting the second error where onAuthStateChanged is not defined
how do I retrieve the user id token?
UPDATE
const getIdTokenRefreshed = async () => {
try {
const user = firebase.auth().currentUser
if (user) {
const token = await user.getIdToken(true)
console.log(`Token: ${token}`)
return token
} else {
console.log("No user is logged in")
}
} catch (e) {
console.log(`Something went wrong: ${e}`)
}
}
after implementing the above code this is the error
await is only valid in async functions and the top level bodies of modules
First, I'd recommend updating Firebase SDK to latest version which is 8.9.1 at the time of writing this.
<script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-auth.js"></script>
If you take a look at onAuthStateChanged part in the documentation, it should be:
firebase.auth().onAuthStateChanged(...)
// ^^
// not firebase.auth.onAuthStateChanged
The onAuthStateChanged won't be triggered unless you call the getIdTokenRefreshed function. You can simply refactor that function to:
const getIdTokenRefreshed = async () => {
try {
const user = firebase.auth().currentUser
if (user) {
const token = await user.getIdToken(true)
console.log(`Token: ${token}`)
return token
} else {
console.log("No user is logged in")
}
} catch (e) {
console.log(`Something went wrong: ${e}`)
}
}
Lastly, the variable name is userToken but in request body it is body: JSON.stringify({idToken: userToke}) and you don't need an await before a variable name. Try refactoring the getUser function to:
//const token = await firebase.auth().currentUser.getIdToken(/* forceRefresh */true).catch(function(error) {
// console.log(error)
//});
//const userToken = await token;
const getUser = async () => {
const token = await firebase.auth().currentUser.getIdToken(true)
const response = await fetch('/getUser', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({idToken: token})
})
const data = await response.json()
console.log(data)
return data
}
getUser().then(data => {
console.log("Data received")
})

How can I set my jwt as a cookie to prevent having to login on refresh?

I am attempting to store a JWT as a cookie to prevent my Axios call creating a new one each time and making me login every time the app is refreshed
I think I am on the right path using JS-Cookie and setting the cookie to my JWT provided by the API. However I am still redirected on login every refresh. How can I keep the authToken as my original JWT token?
import axiosAPI from 'axios';
import Cookies from 'js-cookie';
let authToken = null;
const axios = axiosAPI.create({
baseURL: `${baseURL}`
});
// User login
export const loginUser = (data) => {
return new Promise((resolve, reject) => {
axios.post(`${baseURL}/jwt-auth/v1/token`, data)
.then((res) => {
if (Cookies.get('token') == null) {
authToken = res.data.token;
} else {
Cookies.set('token', res.data.token);
authToken = Cookies.get('token');
}
// Adds the token to the header
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
resolve(res.data);
})
.catch((error) => {
reject(error);
});
});
};
I have also tried this:
import axiosAPI from 'axios';
import Cookies from 'js-cookie';
const authToken = Cookies.get('token');
const axios = axiosAPI.create({
baseURL: `${baseURL}`
});
// User login
export const loginUser = (data) => {
return new Promise((resolve, reject) => {
axios.post(`${baseURL}/jwt-auth/v1/token`, data)
.then((res) => {
if (Cookies.get('token') === null) {
Cookies.set('token', res.data.token);
}
// Adds the token to the header
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
resolve(res.data);
})
.catch((error) => {
reject(error);
});
});
};
which fails to log me in altogether

Axios interceptor request to refresh id token when expired in VueJs

I want to use axios interceptor before every axios call to pass idToken as authorization header with all the axios calls and I want to refresh the idToken if it has expired before any call.
I am using the following code:
axios.interceptors.request.use(function(config) {
var idToken = getIdToken()
var refreshToken = {
"refreshToken" : getRefreshToken()
}
if(isTokenExpired(idToken)){
console.log("==============Reloading")
refresh(refreshToken).then(response=>{
setIdToken(response.idToken)
setAccessToken(response.accessToken)
})
idToken = getIdToken()
config.headers.Authorization = `${idToken}`;
}
else{
config.headers.Authorization = `${idToken}`;
}
return config;
}, function(err) {
return Promise.reject(err);
});
It works fine till the time idToken is valid. When the idToken expires it gets in an infinite loop and the page hangs. Please help me with this. The refresh() which call the refresh API looks like this:
function refresh(refreshToken) {
const url = `${BASE_URL}/user/refresh`;
return axios.post(url,JSON.stringify(refreshToken))
.then(response =>response.data.data)
.catch(e => {
console.log(e);
});
}
I had some similar problem and creating new axios instance to perform refresh token api call resolved the problem (new AXIOS instance is not resolved by defined axios.interceptors.request.use) (of course below code is just a simple example).
Remember to save original request and process it after token has been refreshed:
F.ex my http-common.js
import axios from 'axios'
const AXIOS = axios.create()
export default AXIOS
...
in App.vue:
axios.interceptors.request.use((config) => {
let originalRequest = config
if (helper.isTokenExpired(this.$store.getters.tokenInfo)) {
return this.refreshToken(this.$store.getters.jwt).then((response) => {
localStorage.setItem('token', response.data.token)
originalRequest.headers.Authorization = response.data.token
return Promise.resolve(originalRequest)
})
}
return config
}, (err) => {
return Promise.reject(err)
})
and the refresh token method:
refreshToken (token) {
const payload = {
token: token
}
const headers = {
'Content-Type': 'application/json'
}
return new Promise((resolve, reject) => {
return AXIOS.post('/api/auth/token/refresh/', payload, { headers: headers }).then((response) => {
resolve(response)
}).catch((error) => {
reject(error)
})
})
}
}

Categories

Resources