Nuxt asyncdata axios on refresh loses auth token - javascript

I am calling a get api that gets an array of mail data. It works fine on postman. When I use asyncdata method to get the array. It only works once if user refreshes the page I get 401 error. I pull token from cookies just fine. Normally on non asyncData I do this to set up the header
this.$axios.setHeader('Authorization','Bearer ' + this.$store.state.token);
this.$axios.$post('upload/avatar',formData,{
headers: {'content-type': 'multipart/form-data'}
}).then(res =>{
}).catch(err => console.error(err));{
}
}
This works fine and has no issues
but my asnycData is like this
asyncData(context){
//Cookie has to be read for async to work for now if user disables cookies breaks this page
let token = Cookie.get('token');
context.app.$axios.setHeader('Authorization',`Bearer ${token}`);
return context.app.$axios.$get('get/all/mail').then(mailData =>{
console.log(context.app.$axios.defaults);
let mailMap = [];
//create array to load mail data in
for(let key in mailData){
mailMap.push({...mailData[key]});
}
return{
mailArray:mailMap
}
}).catch(e =>console.error(e));
}
I am trying to make a simple inbox page that can send , delete , and draft messages.

The problem is probably due to the fact that since asyncData is running from the server, it'll lose any browser cookies.
If you're using axios, the nuxt community has setup a middleware module that can be used to automatically inject browser cookies into server requests.

Related

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.

Is there a way to fetch data only when token Is available?

Good day all,
I have a little issue in my react code and I hope someone here would be able to help me fix it.
Basically I am fetching data from an endpoint (multiple)
Am using axios and react query.
In my get request, I have security headers and one is Authorization.
User is authorized by a token sent to session storage when he / she login.
I get the token from session storage and place it in my authorization header.
My problem now is before the user login, the token === null
and my get request runs before the user logins leaving it with an empty token.
When the user finally logins, no data is fetched because what is in the authorization header is null but when I refresh the page, the data is fetched because at that time the token was already delivered and not = null.
I don't know if you fully understand cause am pretty bad at explaining but if you do, is there anyway to fix the issue.
thanks...
If you're using RTK, you may use skip:
const [skip, setSkip] = useState(true);
const { isUninitialized, isSuccess, isLoading } = useSomeQuery(token, {
skip
});
and set skip to false when the token is available
since the token is a dependency to your fetch, it should go to the queryKey. Then you can disable the query via the enabled option for as long as the token is null.
const token = useTokenFromLocalStorage()
useQuery(
["user", token],
() => fetchUser(token),
{
enabled: !!token
}
)
if the token updates (changes from null to a value), and that re-renders your component, the query will run automatically because the query key changes. The query will not run if the token is null.

Next.js redirect from an API route

I am building a back-office app that requires users to sign in.
I have 2 external APIs:
API A : to manage user accounts and sessions
API B : to perform CRUD actions on another database (unrelated to users database)
The problem is that I don't want users to be able to perform calls to API B if their session is not valid. So I added some API endpoints in Next (under pages/api) that do the following actions:
verifying the validity of the session against API A
if session is valid: continue to step 3, if not: redirect to page /login
make the call to API B
Everything works fine if the session is valid but it fails if the session is not valid.
I have tried
res.redirect(307, '/login').end()
and
res.writeHead(307, { Location: '/login' }).end()
but it didn't work. It fails even by specifying the whole path (http://localhost:3000/login). What I don't understand is that I am successfully redirected to my /login page if I make the request directly from the browser (GET http://localhost:3000/api/data). It doesn't work when I make the request with Axios inside a React component.
Any idea how I can fix this?
As #juliomalves and #yqlim explained, I had to make the redirect manually based on the response of the API.
Faced same problem solve using below code:
Api
res.status(200).json({ success: "success" }) //add at last of the api to give response
page
import Router from 'next/router'
let res = await fetch('api', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
if (res.status == 200) {
Router.push('/location')
}
Answer is correct as #Jules Grenier sayes,but provided an example
You do not need .end(). Have you tried res.redirect(307, '/login')?
In Next.js v12 and v13, the following works for me.
// /api/example.js
const handler = async function (req, res) {
// custom logic
if (failed)
return res.redirect(307, '/login')
}
export default handler;
The API request must be initiated by a <form>.
redirect will not work with <fetch>

How to properly deal with JWT in headers

I'm trying to set up JWT in my project (NodeJs Express for the backend, and Javascript in frontend).
So in my backend, I send my token like this when the user logs in :
[...]
return res.send({ token, id: userId })
[...]
And in my frontend, I set the Bearer token in my header like this :
const data = {
email,
password
}
await instance.post('/signin', data)
.then((res) => {
instance.defaults.headers.common['authorization'] = `Bearer ${res.data.token}`
window.location = './account.html'
})
.catch((err) => console.log(err))
My questions are :
Did I do it correctly ?
When I'm redirected to the account.html page, how do I retrieve the token that was set while the log in was made ?
When you assign to instance.defaults.headers.common['authorization'] then subsequent requests made via Ajax using whatever library (I'm guessing Axios) that is from that page will include the authorization header.
Assigning a new value to window.location will trigger navigation and discard the instance object (with the assigned header value).
(As a rule of thumb, if you are going to assign a new value to window.location after making an Ajax request then you should probably replace the Ajax request with a regular form submission).
You could assign the data to somewhere where you can read it in the account.html page (such as local storage).
That won't be available to the server for the request for account.html itself though.
You could assign the data to a cookie. Then it would be available for the request for account.html, but in a cookie and not an authorisation header.
Note that using JWTs in authorization headers is something generally done in Single Page Applications and not traditional multi-page websites.

Cannot create httponly cookie containing jwt in ASP.NET Core and React

I'm trying to implement an authentication scheme in my app. The controller, more specifically the method, responsible for checking user's credentials and generating jwt which it has to put into the httponly cookie afterward looks as follows
[HttpPost]
[Route("authenticate")]
public async Task<IActionResult> Authenticate([FromBody] User user)
{
var response = await _repository.User.Authenticate(user.Login, user.Password);
if (!response) return Forbid();
var claims = new List<Claim>
{
new Claim("value1", user.Login)
};
string token = _jwtService.GenerateJwt(claims);
HttpContext.Response.Cookies.Append(
"SESSION_TOKEN",
"Bearer " + token,
new CookieOptions
{
Expires = DateTime.Now.AddDays(7),
HttpOnly = true,
Secure = false
});
return Ok();
}
I tested this method in Postman - everything works gently and correctly in there. The cookie is being created as well. Moreover, recently I created an app using Angular where I was using the same authentication method, but with Angular's HTTP module the cookie was being created all the time. Here is what that method looks like in my React app with the usage of Axios
export const authenticate = async (login, password) => {
return await axiosLocal.post('/api/auth/authenticate',
{login, password}).then(response => {
return response.status === 200;
}, () => {
return false;
});
Everything I'm getting in response trying to log in is response code 200. I'm pretty sure it's something about Axios's settings.
Also if someone's curios the variable "axiosLocal" contains the baseURL to the API.
- Update 1
Ok. If I'm not mistaken in order to set a cookie from the response I have to send all the requests with { withCredentials: true } option. But when I'm trying to do that the request is being blocked by CORS, although I had already set a cors policy which has to allow processing requests from any origin like that
app.UseCors(builder => builder.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()
.AllowCredentials());
I just had the same issue. I fixed it.
Problem:
In browsers, the httpOnly cookie was received and not returned to the server
In Postman working
// Problemable server code for settings httpOnly cookie
Response.Cookies.Append("refreshToken", refreshToken.Token, new CookieOptions
{
HttpOnly = true,
Expires = DateTime.UtcNow.AddDays(7),
});
Solution:
On the server .AllowCredentials() and
.SetOriginAllowed(host => true) or
.WithOrigins("https://localhost:3000")
On the client (react, axios) withCredentials:true in the headers
If still not working open the Network tab in DevTools in Chrome(current v.91.0.4472.124), select the failed request and when you put the mouse over the yellow triangle you can see very detailed information why the cookie is blocked.
// End server code for setting httpOnly cookie after following the DevTools warnings
Response.Cookies.Append("refreshToken", refreshToken.Token, new CookieOptions
{
HttpOnly = true,
Expires = DateTime.UtcNow.AddDays(7),
IsEssential=true,
SameSite=SameSiteMode.None,
Secure=true,
});
Finally solved. Passing .SetIsOriginAllowed(host => true) instead of .AllowAnyOrigin() to CORS settings with { withCredentials: true } as an option in Axios request helped me.

Categories