I'm using firebase for authentication. Everytime before I do a request to the backend I request for the idToken.
service.interceptors.request.use(async request => {
const token = await firebase.auth().currentUser.getIdToken();
if (!token && request.url !== '/signIn' && request.url !== '/register') {
toast.error('Not authenticated');
return;
}
request.headers.Authorization = 'Bearer ' + token;
return request;
});
Additionally I have a request to the backend that will run in the mounted hook of my vue component.
mounted() {
plantService.getPlants().then(data => (this.suspicionList = data));
}
Usually, this works but the problem is, when I refresh the page, firebase.auth().currentUser is null and the request will fail thus not returning any data.
I already tried creating a computed property and observe that one in a watcher. But not working.
Also, I have an observer for the user in my main.js file like this:
firebase.auth().onAuthStateChanged(user => {
store.dispatch('FETCH_USER', user);
});
Anyone has an idea?
Thanks!
If I correctly understand your problem, the following should do the trick:
mounted() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
plantService.getPlants().then(data => (this.suspicionList = data));
}
});
}
Related
I'm working on a small project using VueJS and Vue Router and Laravel in the backend.
I tried too many times to use navigation guard but didn't succeed,
I have a login component works really fine, this is my login method : (as you can see I'm using localStorage for my token and some other data)
Login.vue
login() {
axios.post('http://proudata.test/api/login', this.user).then(response => {
if (response.data.data.isLogged == true) {
localStorage.setItem('token', JSON.stringify(response.data));
router.push({ name: 'dashboard' });
}
}).catch(error => {
this.error = true;
this.message = error.response.data.message
});
}
my issue is with the navigation guard and exactly in beforeEach, I try to validate the token each time the user click on another page to check if the token is still alive or expired :
this is my routes.js file :
router.beforeEach(async (routeTo, routeFrom, next) => {
const authRequired = routeTo.matched.some((route) => route.meta.authRequired)
if (!authRequired) return next()
// **THE PROBLEM IS HERE IM GETTING 401 JUST AFTER LOGIN**
await client.get('validate-token').then(() => {
next();
}).catch(() => {
next({ name: 'login' })
});
});
and I have an axios client file that I use to send the header each time as you can see :
Client.js
import axios from 'axios';
let userData = localStorage.getItem('token');
let authenticated = {data:{}};
if(userData !== null){
authenticated = JSON.parse(userData);
}
const client = axios.create({
baseURL: ((authenticated.data.domain !== null) ? authenticated.data.domain : 'http://domain.test/api')
});
client.interceptors.request.use(config => {
if(userData !== null){
config.headers.Authorization = authenticated.data.token ? `Bearer ${authenticated.data.token}` : null;
}
return config;
});
export default client;
the problem is : once I click on login I get my response like that : (which is correct, logged successfully )
Server Response (201), logged successfully
{
data:{
isLogged:true,
token: gkljdfslkgjdfklgjfdlkgj,
domain: http://user.multitenancy.test
}
}
then I get an error in validate-token I get 401, is like I'm not sending the token that I have saved in the localStorage as Bearer Authorization (check the interceptor please)
As you can see in my login method, token is saved in the localStorage, but im sending null in the Bearer Authorization, ( the problem only when I push login button to login ) and I see my localStorage data in the Devtools fine.
probably I should not verify the token in beforeEach ? What do you think the issue ?
You need to get token from localStorage every time to get fresh data.
config.baseURL also need to set every time in your interceptor.
import axios from 'axios';
let userData = localStorage.getItem('token');
let authenticated = {data:{}};
if(userData !== null){
authenticated = JSON.parse(userData);
}
const client = axios.create({
baseURL: ((authenticated.data.domain !== null) ? authenticated.data.domain : 'http://domain.test/api')
});
client.interceptors.request.use(config => {
// get token from localStorage every time to get fresh data
let userData = localStorage.getItem('token');
let authenticated = {data:{}};
if(userData !== null){
authenticated = JSON.parse(userData);
config.headers.Authorization = authenticated.data.token ? `Bearer ${authenticated.data.token}` : null;
config.baseURL = ((authenticated.data.domain !== null) ? authenticated.data.domain : 'http://domain.test/api')
}
return config;
});
export default client;
I'm trying to implement an SSO react-admin login
Most of the react-admin examples a is simple username/password login and get token then store at storage. But for me, token will return from Auth Provider server and redirect to http://example.com/#/login?token=TOKEN, and make react-admin to parse the URL, and set token in localStorage. Then update React admin store to mark a user as "is logged in,"
but for me I failed to simulate logged in as a hook after validating token from app main js can you help me how to do that
A workaround like below is works for me, but this is quite dirty solution:
const url = window.location;
(() => {
if (localStorage.getItem("access_token") === "null" || localStorage.getItem("access_token") === "undefined") {
localStorage.removeItem("access_token");
}
})();
const access_token = () => {
if (localStorage.getItem("access_token")) {
return localStorage.getItem("access_token")
} else if (new URLSearchParams(url.search).get("access_token")) {
return new URLSearchParams(url.search).get("access_token")
}
}
localStorage.setItem("access_token", access_token());
export const authProvider = {
login: () => {
return Promise.resolve();
},
logout: () => {
localStorage.removeItem("access_token");
return Promise.resolve();
},
checkError: (status) => {
if (status.response.status === 401 || status.response.status === 403) {
localStorage.removeItem("access_token");
return Promise.reject();
}
return Promise.resolve();
},
checkAuth: () => {
return localStorage.getItem("access_token") ? Promise.resolve() : Promise.reject();
},
getPermissions: () => Promise.resolve(),
};
There is no login method call at all, just direct setting token to the localStorage and check with checkAuth method. Maybe, sombody will give better solution.
I create a simple login form on Angular (v8). On return response, I save it in localStorage like this
this.loginCtrl.login(form.value).subscribe(
response => {
console.log(response["token"]); //IS CORRECT
if (response["token"] != null) {
localStorage.setItem("token", response["token"]);
}
})
Then I want to get the token and send it to other services.
const httpOptions = {
headers: new HttpHeaders({
Authorization: "Token " + localStorage.getItem("token")
})
};
getGroupsByEntityAndUser(id: string, user: String) {
return this.http.get(
"URL" +
id +
"/username/" +
user,
httpOptions
);
}
The problem appears when I load the home page. The console returns that the token is null so the response is null. When I refresh the page with F5 I get the token and getGroupsByEntityAndUser function works properly. It´s a bit strange.
So the question is: Why when I load the first time localStorage is null but when I refresh the page is filled? It is necessary to be filled without refresh.
this.loginCtrl.login(form.value).subscribe(
async (response) => {
await this.handleToken(response);
// Execute your Next Code
});
handleToken(data) {
if (!localStorage.getItem('token')) {
localStorage.setItem('token', data.token);
}
}
The localStorage.getItem-method is asynchronous, please use fat arrow function to catch the result when available, like here:
try {
this.storage.get('eqs').then( eqlist => {
let _entries = JSON.parse(eqlist);
_entries.forEach( el => {
this.savedEQs.push(el);
});
});
} catch (e) {
console.log('no entries found!');
}
}
I am trying to load a notification token (notificationToken) that I've stored within Firebase to a React Native component.
Once the notificationToken is loaded to my redux state, I want to check for my device permissions to see if the notificationToken has expired within the function getExistingPermission() that I run in the componentDidMount().
If the token has expired, then I'll replace the token within Firebase with the new token. If it's the same, then nothing happens (which is intended functionality).
When I'm running my function getExistingPermission() to check if the token is up-to-date the Firebase listener that pulls the notificationToken does not load in time, and so it's always doing a write to the Firebase database with a 'new' token.
I'm pretty sure using async/await would solve for this, but have not been able to get it to work. Any idea how I can ensure that the notificationToken loads from firebase to my redux state first before I run any functions within my componentDidMount() function? Code below - thank you!
src/screens/Dashboard.js
Should I use a .then() or async/await operator to ensure the notificationToken loads prior to running it through the getExistingPermission() function?
import {
getExistingPermission
} from '../components/Notifications/NotificationFunctions';
componentDidMount = async () => {
// Listener that loads the user, reminders, contacts, and notification data
this.unsubscribeCurrentUserListener = currentUserListener((snapshot) => {
try {
this.props.watchUserData();
} catch (e) {
this.setState({ error: e, });
}
});
if (
!getExistingPermission(
this.props.notificationToken, //this doesn't load in time
this.props.user.uid)
) {
this.setState({ showNotificationsModal: true });
}
};
src/components/Notifications/NotificationFunctions.js
The problem is probably not here
export const getExistingPermission = async (
notificationToken,
uid,
) => {
const { status: existingStatus } = await Permissions.askAsync(
Permissions.NOTIFICATIONS
);
if (existingStatus !== 'granted') {
console.log('status not granted');
return false;
} else {
let token = await Notifications.getExpoPushTokenAsync();
/* compare to the firebase token; if it's the same, do nothing,
if it's different, replace */
if (token === notificationToken) {
console.log('existing token loaded');
return true;
} else {
console.log('token: ' + token);
console.log('notificationToken: ' + notificationToken);
console.log('token is not loading, re-writing token to firebase');
writeNotificationToken(uid, token);
return false;
}
}
};
src/actions/actions.js
// Permissions stuff
watchPermissions = (uid) => (
(dispatch) => {
getPermissions(uid + '/notificationToken', (snapshot) => {
try {
dispatch(loadNotificationToken(Object.values([snapshot.val()])[0]));
}
catch (error) {
dispatch(loadNotificationToken(''));
// I could call a modal here so this can be raised at any point of the flow
}
});
}
);
// User Stuff
export const watchUserData = () => (
(dispatch) => {
currentUserListener((user) => {
if (user !== null) {
console.log('from action creator: ' + user.displayName);
dispatch(loadUser(user));
dispatch(watchReminderData(user.uid)); //listener to pull reminder data
dispatch(watchContactData(user.uid)); //listener to pull contact data
dispatch(watchPermissions(user.uid)); //listener to pull notificationToken
} else {
console.log('from action creator: ' + user);
dispatch(removeUser(user));
dispatch(logOutUser(false));
dispatch(NavigationActions.navigate({ routeName: 'Login' }));
}
});
}
);
export const loadNotificationToken = (notificationToken) => (
{
type: 'LOAD_NOTIFICATION_TOKEN',
notificationToken,
}
);
Tony gave me the answer. Needed to move the permissions check to componentDidUpdate(). For those having a similar issue, the component looks like this:
src/screens/Dashboard.js
componentDidUpdate = (prevProps) => {
if (!prevProps.notificationToken && this.props.notificationToken) {
if (!getExistingPermission(
this.props.notificationToken,
this.props.user.uid
)) {
this.setState({ showNotificationsModal: true });
}
}
};
Take a look at redux subscribers for this: https://redux.js.org/api-reference/store#subscribe . I implement a subscriber to manage a small state machine like STATE1_DO_THIS, STATE2_THEN_DO_THAT and store that state in redux and use it to render your component. Only the subscriber should change those states. That gives you a nice way to handle tricky flows where you want to wait on action1 finishing before doing action2. Does this help?
I would like to know if it is possible to do this, because I'm not sure if I'm wrong or if it isn't possible. Basically, what I want to do is to create a wrap function for native fetch javascript function. This wrap function would implement token validation process, requesting a new accessToken if the one given is expired and requesting again the desired resource. This is what I've reached until now:
customFetch.js
// 'url' and 'options' parameters are used strictely as you would use them in fetch. 'authOptions' are used to configure the call to refresh the access token
window.customFetch = (url, options, authOptions) => {
const OPTIONS = {
url: '',
unauthorizedRedirect: '',
storage: window.sessionStorage,
tokenName: 'accessToken'
}
// Merge options passed by user with the default auth options
let opts = Object.assign({}, OPTIONS, authOptions);
// Try to update 'authorizarion's header in order to send always the proper one to the server
options.headers = options.headers || {};
options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;
// Actual server request that user wants to do.
const request = window.fetch(url, options)
.then((d) => {
if (d.status === 401) {
// Unauthorized
console.log('not authorized');
return refreshAccesToken();
}
else {
return d.json();
}
});
// Auxiliar server call to get refresh the access token if it is expired. Here also check if the
// cookie has expired and if it has expired, then we should redirect to other page to login again in
// the application.
const refreshAccesToken = () => {
window.fetch(opts.url, {
method: 'get',
credentials: 'include'
}).then((d) => {
// For this example, we can omit this, we can suppose we always receive the access token
if (d.status === 401) {
// Unauthorized and the cookie used to validate and refresh the access token has expired. So we want to login in to the app again
window.location.href = opts.unauthorizedRedirect;
}
return d.json();
}).then((json) => {
const jwt = json.token;
if (jwt) {
// Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
opts.storage.setItem(opts.tokenName, jwt);
console.log('new acces token: ' + jwt);
// Re-send the original request when we have received the refreshed access token.
return window.customFetch(url, options, authOptions);
}
else {
console.log('no token has been sent');
return null;
}
});
}
return request;
}
consumer.js
const getResourcePrivate = () => {
const url = MAIN_URL + '/resource';
customFetch(url, {
method: 'get'
},{
url: AUTH_SERVER_TOKEN,
unauthorizedRedirect: AUTH_URI,
tokenName: TOKEN_NAME
}).then((json) => {
const resource = json ? json.resource : null;
if (resource) {
console.log(resource);
}
else {
console.log('No resource has been provided.');
}
});
}
I'll try to explain a little better the above code: I want to make transparent for users the token validation, in order to let them just worry about to request the resource they want. This approach is working fine when the accessToken is still valid, because the return request instruction is giving to the consumer the promise of the fetch request.
Of course, when the accessToken has expired and we request a new one to auth server, this is not working. The token is refreshed and the private resource is requested, but the consumer.js doesn't see it.
For this last scenario, is it possible to modify the flow of the program, in order to refresh the accessToken and perform the server call to get the private resource again? The consumer shouldn't realize about this process; in both cases (accessToken is valid and accessToken has expired and has been refreshed) the consumer.js should get the private requested resource in its then function.
Well, finally I've reached a solution. I've tried to resolve it using a Promise and it has work. Here is the approach for customFetch.js file:
window.customFetch = (url, options, authOptions) => {
const OPTIONS = {
url: '',
unauthorizedRedirect: '',
storage: window.sessionStorage,
tokenName: 'accessToken'
}
// Merge options passed by user with the default auth options
let opts = Object.assign({}, OPTIONS, authOptions);
const requestResource = (resolve) => {
// Try to update 'authorizarion's header in order to send always the proper one to the server
options.headers = options.headers || {};
options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;
window.fetch(url, options)
.then((d) => {
if (d.status === 401) {
// Unauthorized
console.log('not authorized');
return refreshAccesToken(resolve);
}
else {
resolve(d.json());
}
});
}
// Auxiliar server call to get refresh the access token if it is expired. Here also check if the
// cookie has expired and if it has expired, then we should redirect to other page to login again in
// the application.
const refreshAccesToken = (resolve) => {
window.fetch(opts.url, {
method: 'get',
credentials: 'include'
}).then((d) => {
if (d.status === 401) {
// Unauthorized
window.location.href = opts.unauthorizedRedirect;
}
return d.json();
}).then((json) => {
const jwt = json.token;
if (jwt) {
// Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
opts.storage.setItem(opts.tokenName, jwt);
console.log('new acces token: ' + jwt);
// Re-send the original request when we have received the refreshed access token.
requestResource(resolve);
}
else {
console.log('no token has been sent');
return null;
}
});
}
let promise = new Promise((resolve, reject) => {
requestResource(resolve);
});
return promise;
}
Basically, I've created a Promise and I've called inside it to the function which calls to server to get the resource. I've modified a little the request(now called requestResource) and refreshAccessToken in order to make them parametrizable functions. And I've passed to them the resolve function in order to "resolve" any function once I've received the new token.
Probably the solution can be improved and optimized, but as first approach, it is working as I expected, so I think it's a valid solution.
EDIT: As #Dennis has suggested me, I made a mistake in my initial approach. I just had to return the promise inside the refreshAccessToken function, and it would worked fine. This is how the customFetch.js file should look (which is more similar to the code I first posted. In fact, I've just added a return instruction inside the function, although removing the start and end brackets would work too):
// 'url' and 'options' parameters are used strictely as you would use them in fetch. 'authOptions' are used to configure the call to refresh the access token
window.customFetch = (url, options, authOptions) => {
const OPTIONS = {
url: '',
unauthorizedRedirect: '',
storage: window.sessionStorage,
tokenName: 'accessToken'
}
// Merge options passed by user with the default auth options
let opts = Object.assign({}, OPTIONS, authOptions);
// Try to update 'authorizarion's header in order to send always the proper one to the server
options.headers = options.headers || {};
options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;
// Actual server request that user wants to do.
const request = window.fetch(url, options)
.then((d) => {
if (d.status === 401) {
// Unauthorized
console.log('not authorized');
return refreshAccesToken();
}
else {
return d.json();
}
});
// Auxiliar server call to get refresh the access token if it is expired. Here also check if the
// cookie has expired and if it has expired, then we should redirect to other page to login again in
// the application.
const refreshAccesToken = () => {
return window.fetch(opts.url, {
method: 'get',
credentials: 'include'
}).then((d) => {
// For this example, we can omit this, we can suppose we always receive the access token
if (d.status === 401) {
// Unauthorized and the cookie used to validate and refresh the access token has expired. So we want to login in to the app again
window.location.href = opts.unauthorizedRedirect;
}
return d.json();
}).then((json) => {
const jwt = json.token;
if (jwt) {
// Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
opts.storage.setItem(opts.tokenName, jwt);
console.log('new acces token: ' + jwt);
// Re-send the original request when we have received the refreshed access token.
return window.customFetch(url, options, authOptions);
}
else {
console.log('no token has been sent');
return null;
}
});
}
return request;
}