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.
Related
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.
Trying out SvelteKit and I'm having a hard time with hooks. The docs don't really seem to explain it all too well. My current understanding of hooks is that they basically allow you to interact with requests made to your server before they get to their destination? (I'm open to a better explanation - specifically the handle hook).
My current issue is I made an endpoint called login. As the name suggests, it allows users to sign into my application by generating a token and storing it as a cookie on their client. This works until I add hooks. After reading the hooks description, I figured the handle hook is perfect for what I want to do - validate the token on each request - if invalid, reroute the user to the login screen, if valid, allow the request to continue uninterrupted.
export const handle: Handle = async ({ event, resolve }) => {
const isLogin = event.url.pathname.startsWith('/login')
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
const token = cookies['token']
if (!token) {
if (!isLogin) {
return Response.redirect(`${event.url.origin}/login`)
}
return await resolve(event)
} else {
try {
await verifyToken(token)
if (isLogin) {
return Response.redirect(`${event.url.origin}/about`)
}
} catch (err) {
return Response.redirect(`${event.url.origin}/login`)
}
}
return await resolve(event)
};
This does not work as expected. When I initiate the request to the api/login endpoint, the request does not seem to make it there. I have console.logs all over the endpoint but no messages were outputted to the terminal & when I check the application storage, no new cookie was added.
What am I missing about hooks?
Why is it not passing the request off to the endpoint?
Any idea how I can fix this?
The handle hook runs for every request—including endpoints.
When you fetch /api/login without a token, your hook will redirect the request to /login since isLogin === false. You need to allow through every route that should be accessible without a login, for example:
const isLogin = /^\/(api\/)?login$/.test(event.url.pathname)
I'm building a web app with CodeIgniter 4 where I implemented a REST API for register and login and a profile page just a test my login.
I have a login form that sends a javascript fetch post request to my api and I receives a jwt token. This is working.
Now I am in the situation where I think I did not understand the principle.
I want that the user stays logged in and doesn't need to re-login every time. The token expires after 12h.
And I want to use php (if possible) as the entire app runs on php.
Currently, I have a little javascript function to store my token:
const store = {};
store.setJWT = (data) => {
this.JWT = data;
};
But this is not secure against page reload.
Additionally I am creating a cookie with php, when the user logs in:
helper('cookie');
set_cookie([
'name' => 'login',
'value' => $token,
'expire' => $exp,
'httponly' => true,
'secure' => true,
]);
I am able to fetch data from the API using the cookie or the store object.
const token = getCookie('login');
const res = await fetch('/profile', {
headers: {
'Authorization': `Bearer ${token}` // or store.JWT
}
});
So.... what I want is:
The user goes to a protected url e.g. https://myapp.com/profile and if he is logged in, he has access. If not, he gets redirect to the login page.
Is using the cookie to store the jwt a good idea? Or did I completely misunderstood the idea of JWT and it is not used to be used for a login?
Additionally: I still don't know if biulding the login as an API was the best idea.
First of all there is nothing wrong with building "login with API". It is common practice. And JWT is perfectly suited for auth.
You sure can store JWT token inside a cookie, but it is a little bit wrong in my opinion. Usually JWT tokens are stored in the local storage on the client side. It will persist after page reload.
Set token in the local storage:
localStorage.setItem('token', token);
Get token from the local storage:
token = localStorage.getItem('token');
To better understand the conception of the JWT you can copy some token (without Bearer) and paste it in jwt.io. Basically JWT contain all the required information about the user and server can trust this information.
And when you set the cookie like this
set_cookie([
'name' => 'login',
'value' => $token,
'expire' => $exp,
'httponly' => true,
'secure' => true,
]);
It is an overkill. JWT possible already contains all this information and you can extract it from the token.
I am trying to use firebase authentication to get a google access token in order to call the youtube API.
I am able to initially get the access token like this:
const provider = firebase.auth.GoogleAuthProvider();
cosnt scopes = []; // ...scopes
scopes.forEach(scope => provider.addScope(scope));
firebase.auth().signInWithPopUp(provider)
.then(userCredentials=> {
const oauthCredentials = userCredentials.credentials;
// using credentials for API calls
axios.get("some-google-api-path", { params: { access_token: oauthCredentials.accessToken } }); // and so on...
});
This works fine until the access token expires.
As far as I can tell, it seems like firebase automatically refreshes the session, but I can find a way to get the new access token.
I tried:
firebase.auth().onAuthStateChange(user => {
// could not find the access token on the user object
});
And since that failed, I tried to do it manually using:
const token = firebase.auth.GoogleAuthProvider.credential(
oauthCredentials.idToken,
oauthCredentials.accessToken
);
const authResult = await firebase.auth().signInWithCredential(token);
The issue is that authResult will only contain the idToken or the accessToken, but not both, depends on what I give the firebase.auth.GoogleAuthProvider.credential function.
Am I missing something?
Is there another/better way to do it?
There are two tokens in play here:
The refresh token, which is only available after the user actively signs in. Since it doesn't change, you can cache this token - which is what the Firebase SDKs actually also do.
The ID token, which is available at any moment from the current user. This token expires in an hour, and is auto-refreshed by the Firebase SDKs.
So if you want to retain access to the refresh token, you will have to store (typically in local storage) yourself when the user first actively signs in.
So apparently firebase doesn't refresh tokens of other providers for you (not event google) according to this (thank you Frank van Puffelen!)
So what I did instead, is authenticate manually to google (since I use react, I used react-google-login), and got all tokens from there.
Then, once the session is initiated/refreshed, I create a firebase session using:
const token = firebase.auth.GoogleAuthProvider.credential(
oauthCredentials.idToken,
oauthCredentials.accessToken
);
const authResult = await firebase.auth().signInWithCredential(token);
I hope that this helps anyone, and I will accept another answer if firebase ever changes this.
I am building a react native application and am using Firebase, more specifically firestore, in order to manage my data. My current objective is to implement an auto login feature on my app, where if the user exits the app, I want them to stay signed in, unless they manually hit the Sign Out button before exiting the app. Here is my current process of doing this:
When the user logs into the app, I sign them in by:
firebase.auth().signInWithEmailAndPassword(email, password).
I then get their idToken by:
let authIdToken = "";
firebase
.auth()
.currentUser.getIdToken(true)
.then(function (idToken) {
authIdToken = idToken
})
.catch(function (error) {
console.log(error)
});
I then want to save this token into the phone, so when the user opens the app again, I can fetch this token and check its validity. If it is valid, then I can log the user in using their idToken. In react native, I can do this by doing:
AsyncStorage.setItem(
"userData",
JSON.stringify({
token: token,
})
);
Now when the app loads up:
const startScreen = props => {
useEffect(() => {
const tryLogin = async () => {
const userData = await AsyncStorage.getItem("userData");
const transformedData = JSON.parse(userData);
const { token } = transformedData;
await firebase
.auth()
.verifyIdToken(token, true)
.then((payload) => {
console.log(true)
})
.catch((error) => {
if (error.code == "auth/id-token-revoked") {
// Token has been revoked. Inform the user to reauthenticate or signOut() the user.
console.log("revoked")
} else {
console.log("error")
}
});
};
tryLogin();
}, []);
The Issue: When I try to verify the token this way, I am met with the following error: firebase.auth().verifyIdToken is not a function.
I read through the documentation and am unsure of how else to verify this token using JS. How do I verify it? Let me know if my verification process is incorrect and how it should be done. I am new to using firestore and doing authentication in general and hope to learn how to do it the right way.
Another helpful note: This is how I am configuring my firestore: !firebase.apps.length ? firebase.initializeApp(firebaseConfig) : {};
Thanks!
I then want to save this token into the phone, so when the user opens the app again, I can fetch this token and check its validity.
This is completely unnecessary. Firebase Auth with persist the signed in user, and automatically refresh the token without you having to do anything. All you need to do is listen to when updates to the token are made available, and act on the new token as needed. You can establish an ID token listener using onIdTokenChanged as shown in the linked API documentation:
firebase.auth().onIdTokenChanged(function(user) {
if (user) {
// User is signed in or token was refreshed.
}
});
Once you have this token, you know that the user is successfully signed in. There is nothing left to do. There is no need to use it to sign in.
Also, you can't verify the token on the frontend. The verifyIdToken method you're looking at is for the Admin SDK only, which only runs on the backend. The idea is that you get the token on the fronend, then pass it to the backend as described in the documentation for the Admin SDK. The backend uses this to securely determine if the user on the frontend is who they say they are.
Since you didn't say if you have a backend or not, dealing with this token might not be necessary at all. If you just want to know when the user is signed in (even if they are just returning to the page after being away, then you can skip everything above and just use an auth state observer. Again, Firebase Auth persists information about the user so you don't have to sign them in again. The observer will tell you when the automatic sign-in is complete, or if they are not signed in at all.