Pass firebase access token to backend in axios interceptor - javascript

I need some help understanding how to pass in a firebase bearer/jwt token with my api requests to the backend.
I am using axios and using an interceptor to set up the bearer token similar to this
axios.interceptors.request.use(
config => {
const userData = store.getters['firebaseAuth/user']
if (userData && userData.accessToken) {
config.headers['Authorization'] = 'Bearer ' + userData.accessToken
}
In all the examples for firebase auth, I have seen that the way to get the jwt token is like so:
firebase
.auth()
.currentUser.getIdToken(/* forceRefresh */ true)
.then(idToken => {
Would it be good practice to run this in my interceptor to obtain the access token?..
Feels like an unnecessary extra api call to firebase servers for every request made to my backend server.
Also I have trouble understanding why I should even use this firebase method (getIdToken) when the token is available as a property from the user object. It has a strange name "za" which i think is deliberate from firebase, and nowhere mentions to use this to get the id/bearer/access token.
firebase.auth().onAuthStateChanged(user => {
if (user) {
//user.za id the id/bearer/access token
My initial thought was to just store user.za in localstorage then fetch it from local storage in the interceptor.

I'm not sure if it is the best way, but I ended up using the Firebase SDK methods in my interceptor directly. My understanding of the getIdToken API is that the promise returns a cached JWT, unless it is expired, in which case it will return a fresh one. I made the interceptor function async, and use await to synchronously request the token.
axios.interceptors.request.use(
async function (request) {
const user = currentUser();
if (user) {
try {
const token = await user.getIdToken(false); // Force refresh is false
request.headers["Authorization"] = "Bearer " + token;
} catch (error) {
console.log("Error obtaining auth token in interceptor, ", error);
}
}
return request;
},...

Related

How to get userId on client without storing the jwt on client?

Im using jwt in my react native/node app. I do not store it on the client and dont want/need to. In one case I need to know the userId on client but only have the token (that i use to create jwt). I need it because I need to establish a connection only with specific users so I need to get the userId.
How can I do that without storing the jwt on client? Or do I just use the token on client, send that, and in my backend I verify the token to get the actual userId and then compare?
client:
const instance = axios.create({
baseURL: 'example.com'
});
instance.interceptors.request.use(
async (config) => {
const token = await AsyncStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(err) => {
return Promise.reject(err);
}
backend
app.use(router.get('/stream', requireAuth));
in requireAuth create the jwt
jwt.verify(token, ...
and attach it to req.user.
The best way to transfer a token from the BE to FE will be using HTTP-only cookies, with this you will not be able to read the token in FE using JS but it will automatically send with every request and you will be able to read it in the BE and get the userId and do relevant things with it.
Using HTTP cookies

Application Not Loading Token from LocalStorage After Sign In But Works After Refresh

I have an angular application and for some reason, the localstorage is showing the token from current User as null and therefore, the api return a 401 unauthorized. The issue happens after login and saving the token to localStorage and routes to the proper component to handle the next request. The request at this next page after login is returning 404 because the jwt did not find the token in the localstorage. Once I refresh the page, the api's start working again as if it found the token.
I have tried multiple approaches such as trying to inject localstorage, try async localstorage get, etc but nothing seems to work. Any help would be appreciated.
my JWT Interceptor is:
export class JwtInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add authorization header with jwt token if available
let currentUser = JSON.parse(localStorage.getItem('currentUser'));
if (currentUser && currentUser.token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.token}`
}
});
}
return next.handle(request);
}
}
The authentication Code is t which I get and save to user is(the use of this function in the component is under this):
login(merchantId: string, client_id: string, code: string) {
return this.http.post<any>(`${environment.apiUrl}/cloverApi/authenticate`, {merchantId: merchantId, client_id: client_id, code: code})
.pipe(map(token => {
// login successful if there's a jwt token in the response
this.role = token.role;
if (token && token.access_token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
this.setCookie('clover_access_token_cookie', 'Generic User', 364); //expires in 364 days as token only last 365
console.log('in clover auth.service login completed. Cookie Stored under username: ' + this.getLoggedInSubject()); //*MES*
return token;
}
}));
}
So I get the clover api token and set it to the current user and send an update request as shown here:
this.cloverTokenService.login(this.url_merchant_id, this.url_client_id, this.url_code)
.subscribe(
data => {
this.currentUser.cloverToken = data.access_token;
this.currentUser.merchantId = this.url_merchant_id;
this.userService.update(this.currentUser).subscribe((updatedUser:User)=> {
localStorage.setItem('currentUser', JSON.stringify(this.currentUser));
localStorage.setItem('', '');
this.checkAuthAndRoute();
},
error => {
console.error(error);
})
},
error => {
console.error(error);
});
While setting your object in localStorage your token is under currentUser.cloverToken then when you retrieve it your trying to read currentUser.token
Either do this this.currentUser.token= data.access_token; or this Authorization: Bearer ${currentUser.cloverToken}
I believe you can make use of BehaviorSubject to pass token around as getting data from localstorage could have caused the issue somehow. You can next to the behavior subject when login succeeds and that can be used by the interceptor.
Note that you're using this.currentUser instead of the updatedUser inside the subscribe method.
And you are not setting this.currentUser.token anywhere in the snippets you provided. So, it's going to be undefined when you use it in your interceptor unless you are setting it somewhere else.
To confirm this, check your local storage from the browser. For example, in Chrome, it would be under the Application tab.
If currentUser appears there but doesn't have the token property then it's what I already mentioned.
If currentUser appears in the local storage with the token property, then you're probably not setting your interceptor correctly. In that case, you may find this article about interceptors helpful.
For example, I don't know if you omitted this on purpose but I don't see your interceptor annotated with the #Injectable() decorator.

Refresh and access token flow in JWT

I have developed a standard JWT system that logs in and issues an access token and a refresh token. The access token expires after a short amount of time and there is a route to refresh the token.
I use axios to make requests but I am not sure how to deal with expired tokens. For example, if I make a request to /secret_route with my access token and it's expired, do I need to wait for a 403 and then make a request to /refresh_token and then make the original request again? Seems messy from a programming point of view and quite wasteful on the network. Is there an efficient/elegant way to do this?
I ended up with a solution that I feel is more robust than checking the timestamp. Thanks #Bergi but I am concerned about the system clock. I use axios interceptors to refresh the token on a 401
// Request interceptor for API calls
axios.interceptors.request.use(
async config => {
config.headers = {
'Authorization': `Bearer ${localStorage.getItem("accessToken")}`,
'Accept': 'application/json',
}
return config;
},
error => {
Promise.reject(error)
});
// Allow automatic updating of access token
axios.interceptors.response.use(response => response, async (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const res = await axios.post('/users/token', { token: localStorage.getItem('refreshToken') });
setToken(res.data.accessToken);
return axios.request(originalRequest);
}
return Promise.reject(error);
});
Adapted from https://thedutchlab.com/blog/using-axios-interceptors-for-refreshing-your-api-token

How can I store a JWT in cookies using Axios?

I am using JWT in my React application and Axios to handle API calls. I am looking for a way to store the token in cookies so that I am not redirected to login again every time the browser is refreshed.
Here is my setup for Axios and my login call:
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) => {
authToken = res.data.token;
// Adds the token to the header
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
resolve(res.data);
})
.catch((error) => {
reject(error);
});
});
};
I am not certain where I should be setting the cookie and how to set it?
EDIT:
I have rewritten my code using js-cookie so it looks like the comment.
import axiosAPI from 'axios';
import Cookies from 'js-cookie';
let authToken = null;
const axios = axiosAPI.create({
baseURL: `${baseURL}`
});
// Check if user is logged in.
(function () {
if (Cookies.get('token') === null) {
// This means that there's no JWT and no user is logged in.
axios.defaults.headers.common.Authorization = null;
} else {
// This means that there's a JWT so someone must be logged in.
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
}
}());
// User login
export const loginUser = (data) => {
return new Promise((resolve, reject) => {
axios.post(`${baseURL}/jwt-auth/v1/token`, data)
.then((res) => {
authToken = res.data.token;
Cookies.setToken('token', authToken);
// Adds the token to the header
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
resolve(res.data);
})
.catch((error) => {
reject(error);
});
});
};
However this prevents me from logging in at all and I get the error 'wrong number of segments'. Any idea why this isn't working?
There are a few different options that you can take here to solve the problem you are having, which is simply finding somewhere to store the JWT so that you can use it even after you refresh the page.
Save the JWT in localStorage or sessionStorage in your axios.post callback so that you can have access to it even after the page refreshes. To learn which storage is most suitable for your app, see this.
To keep it short, values stored in localStorage will persist until you explicitly delete them (you can do this via your JS code). Also, any tabs you open in your browser to that domain will have access to this (very useful if you want to still be logged in with the new tabs). On the other hand, values stored in sessionStorage only live until the tab is closed. They can't be shared across tabs either.
Using this is as simple as:
localStorage.setItem("JWT", authToken); or sessionStorage.setItem("JWT", authToken); in your callback after your authToken = res.data.token;
So now that you have a place where you have stored the JWT, all you need to do is make sure to check if a JWT exists in storage when your app initializes on page load (or refresh). Here's a basic example of how to use localStorage:
// This should be one of the first things that run on your app.
const axios = axiosAPI.create({
baseURL: baseURL
});
// Check if user is logged in.
(function() {
let authToken = localStorage.getItem("JWT");
if (authToken === null) {
// This means that there ISN'T JWT and no user is logged in.
axios.defaults.headers.common.Authorization = null;
} else {
// This means that there IS a JWT so someone must be logged in.
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
}
})();
This will make sure that the user is not logged out on page load, if previously logged in.
Save the JWT in a client side cookie. Here, the cookie is being used as a storage mechanism since you are not actually working with server side cookies given that your authentication is all build around JWT. You can follow the same code pattern as above but instead will be using document.cookie = "key=value" to set a cookie and document.cookie to view all cookies.
This second way is less common because it forces you to do a lot of manual labor like parsing through all the cookies and making sure to set the proper cookie path attribute so that the cookie only gets sent up for the required endpoints (otherwise you're just creating unnecessary overhead). If you exercise this option, read this to help you create your cookie to fit your needs. You can also use a helper JS library like js-cookie to help manipulate client side cookies.
Additionally, I would read through https://stackoverflow.com/a/40376819/11048825 to dive further into these two options and understand the pros and cons associated with each.

Firebase admin sdk: idToken OR custom token verification for authorization

I'm creating a custom token on backend server while login and below is the code:
UserSchema.methods.generateAuthToken = function() {
var user = this;
var access = "auth";
return firebaseAdmin.default
.auth()
.createCustomToken(user._id.toHexString())
.then(function(token) {
user.tokens = user.tokens.concat([{ access, token }]);
return user.save().then(() => {
return token;
});
});
};
and storing this token inside mongodb and sending it back to client via header named "x-auth" and then stored it inside cookies.
On client side, using this token to signin like below:
axios({
url: "/api/users/login",
method: "POST",
data: {
email: data.email,
password: data.password
}
}).then(res => {
Cookies.set("x-auth", res.headers["x-auth"], { expires: 7 });
return firebase
.auth()
.signInWithCustomToken(res.headers["x-auth"])
.then(response => {
console.log("CA", response);
response
.getIdToken()
.then(idToken => {
Cookies.set("idToken", idToken, { expires: 7 });
})
.catch(e => {});
dispatch(
login({
token: Cookies.get("x-auth")
})
);
});
});
Now in order to call an api to fetch all users, I'm sending these tokens, custom token and idToken back to server:
const authenticate = (req, res, next) => {
const token = req.header("x-auth");
const idToken = req.header("idToken");
User.findByToken(token, idToken)
.then(user => {
if (!user) {
return Promise.reject({
message: "no user found with the associated token!"
});
}
req.user = user;
req.token = token;
next();
})
.catch(e => {
res.status(401).send(setErrorMessage(e.message));
});
};
And findByToken is as below:
UserSchema.statics.findByToken = function(token, idToken) {
var User = this;
return firebaseAdmin.default
.auth()
.verifyIdToken(idToken)
.then(function(decodedToken) {
var uid = decodedToken.uid;
return User.findOne({
_id: uid,
"tokens.access": "auth",
"tokens.token": token
});
})
.catch(function(e) {
return Promise.reject(e);
});
};
Why do I have to send both these tokens for authorization, is there anything wrong with the concept here.
https://firebase.google.com/docs/auth/admin/verify-id-tokens
Warning: The ID token verification methods included in the Firebase Admin SDKs are meant to verify ID tokens that come from the client SDKs, not the custom tokens that you create with the Admin SDKs. See Auth tokens for more information.
Please clarify if I can verify the custom token instead of idToken to retrieve userId and match it up with DB, instead of using different tokens for one purpose or I'm doing something wrong here and custom token should not be stored inside DB, and there is some other approach to it.
And now after sometime when I try to fetch all users, it says:
Firebase ID token has expired. Get a fresh token from your client app and try again (auth/id-token-expired). See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
When working with tokens, you need to differentiate each token and what it is used for, also each token has related properties.
Custom Tokens: These tokens will be generated in the server (as you are doing) and are used in the client side to authenticate the client. These tokens expire after one hour.
Session ID Token: (I'm calling it "Session ID Token" just to differentiate it) When the client autheticates with any of the authentication providers, the SDK will exchange the information for an ID token used for the session. The same applies for custom tokens, where the SDK exchanges the custom token for an ID Token. ID Tokens are also short live and will expire after one hour. When getting the ID Token, the SDK also receives a refresh Token which is used for refreshing the session ID Token. The ID token is used when doing authenticated requests to Firebase.
ID Token for verification: To get this token, you will need to call the function "getIDToken" (for Web). This function will return an ID Token that can be used only to verify requests coming from the client to your server. Similar to the other tokens, this one expires after one hour. When calling the getIDToken, the function will request a new one if the current is expired.
To verify the ID Token, you must use the mentioned token. If you try to use the session ID Token or the Custom token, you will get errors. The same applies if you try to use an expired ID Token.
All the calls and tasks to refresh the ID Tokens are handled by the SDKs behind the scenes.
It is not a good practice to store these tokens as they will expire after one hour, is better if you call the appropriate functions to get the latets tokens and pass the correct ID Token to be verified.
Lastly, you can find in these links the properties used for generating the custom token and the ID Token for verification.

Categories