like-gold
like-gold2y ago

How to implement jwt authentication in authProvider?

I have followed this link https://refine.dev/docs/api-reference/core/providers/auth-provider/#setting-authorization-credentials to set my authorization credentials and it is working fine. But now how do I use a refresh token to generate a new access token on every request. What I have done so far is below:
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
const prevRequest = error?.config;
if (error?.response?.status === 401 && !prevRequest?.sent) {
prevRequest.sent = true;
axiosInstance.post(
`${API_BASE_URL}/token/refresh/`,
{ refresh: localStorage.getItem(TOKEN_REFRESH_KEY) }
).then((res => {
prevRequest.headers = { ...prevRequest.headers, ...authHeader(res.data.access) };
return axiosInstance(prevRequest);
}))
.catch((err) => {
if(err.response.status === 401) {
sessionStorage.removeItem(TOKEN_KEY);
return Promise.reject(new Error('Your login session expired. Please login again.'));
}
return Promise.reject(err);
});
}
const customError: HttpError = {
...error,
message: error.response?.data?.message,
statusCode: error.response?.status,
};
return Promise.reject(customError);
},
);
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
const prevRequest = error?.config;
if (error?.response?.status === 401 && !prevRequest?.sent) {
prevRequest.sent = true;
axiosInstance.post(
`${API_BASE_URL}/token/refresh/`,
{ refresh: localStorage.getItem(TOKEN_REFRESH_KEY) }
).then((res => {
prevRequest.headers = { ...prevRequest.headers, ...authHeader(res.data.access) };
return axiosInstance(prevRequest);
}))
.catch((err) => {
if(err.response.status === 401) {
sessionStorage.removeItem(TOKEN_KEY);
return Promise.reject(new Error('Your login session expired. Please login again.'));
}
return Promise.reject(err);
});
}
const customError: HttpError = {
...error,
message: error.response?.data?.message,
statusCode: error.response?.status,
};
return Promise.reject(customError);
},
);
After using this code I can get new access token there isn't any problem. But once the refresh token is expired then how can I logout user. By using above code the request for refresh token is request infinitely.
Auth Provider | refine
refine let's you set authentication logic by providing the authProvider property to the `` component.
29 Replies
Omer
Omer2y ago
Hey @dipbazz , @yildirayunlu can help us 🎯
like-gold
like-gold2y ago
Okay @Omer.
deep-jade
deep-jade2y ago
Hi @dipbazz I did this using the axios-auth-refresh package. I am posting the authProvider as an example.
// Function that will be called to refresh authorization
const refreshAuthLogic = () => {
const auth = getAuth();
if (auth && auth.refreshToken) {
const { refreshToken } = auth;

return axios
.post<IAuth>("/api/auth/refresh-token", {
refreshToken,
})
.then(({ data }) => {
setToken(data);

const { accessToken } = data;
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${accessToken}`;

return Promise.resolve();
})
.catch(() => {
logout();
return Promise.reject();
});
}
logout();
return Promise.reject();
};

createAuthRefreshInterceptor(axios, refreshAuthLogic);
// Function that will be called to refresh authorization
const refreshAuthLogic = () => {
const auth = getAuth();
if (auth && auth.refreshToken) {
const { refreshToken } = auth;

return axios
.post<IAuth>("/api/auth/refresh-token", {
refreshToken,
})
.then(({ data }) => {
setToken(data);

const { accessToken } = data;
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${accessToken}`;

return Promise.resolve();
})
.catch(() => {
logout();
return Promise.reject();
});
}
logout();
return Promise.reject();
};

createAuthRefreshInterceptor(axios, refreshAuthLogic);
You should follow this method in authProvider.checkAuth as well.
checkAuth: async () => {
// qs accessToken && refreshToken check
const params = new URLSearchParams(window.location.search);
const accessToken = params.get("accessToken");
const refreshToken = params.get("refreshToken");

if (accessToken && refreshToken) {
const { data, status } = await axios.get<IUser>(`/api/auth/me`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});

if (status === 200) {
setToken({
...data,
accessToken,
refreshToken,
});
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${accessToken}`;

// TODO: Must be better solution
window.location.href = "/";
return Promise.resolve();
}
}

const auth = getAuth();
if (auth) {
const { accessToken } = auth;
axios.defaults.headers.common["Authorization"] = `Bearer ${accessToken}`;
return Promise.resolve();
}

return Promise.reject();
},
checkAuth: async () => {
// qs accessToken && refreshToken check
const params = new URLSearchParams(window.location.search);
const accessToken = params.get("accessToken");
const refreshToken = params.get("refreshToken");

if (accessToken && refreshToken) {
const { data, status } = await axios.get<IUser>(`/api/auth/me`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});

if (status === 200) {
setToken({
...data,
accessToken,
refreshToken,
});
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${accessToken}`;

// TODO: Must be better solution
window.location.href = "/";
return Promise.resolve();
}
}

const auth = getAuth();
if (auth) {
const { accessToken } = auth;
axios.defaults.headers.common["Authorization"] = `Bearer ${accessToken}`;
return Promise.resolve();
}

return Promise.reject();
},
We are preparing a sample document on this subject. We will publish it soon.
like-gold
like-gold2y ago
Hi @yildirayunlu thank you for your response. I have some question regarding refreshAuthLogic function you have created. In this function where you are catching an error from axios request how exactly is logout() function logging out the user. Can I know the logout code logic implementation? I am completely stuck on how can I log out the current user when the refresh token is expired.
Omer
Omer2y ago
The logout function only clears localstorage. The important point here is that Promise.reject is returned. So it falls into the checkError function of your authProvider. Then the user will be considered logged out already https://refine.dev/docs/api-reference/core/providers/auth-provider/#checkerror
like-gold
like-gold2y ago
Okay Thank you @Omer
Omer
Omer2y ago
It's works now?
like-gold
like-gold2y ago
I will test and let you know. Thank you @Omer and @yildirayunlu it is working now. Whenever the refresh token is expired user will be redirected to login page. But now I am facing a new issues. Whenever the user is logged out after the refresh token is expired they will be redirected to login page but the redirect url will be to=/login instead of redirect url as the resource that I am trying to access. For example: let us say I am trying to access the users resource from the menu but my refresh token is expired then I will be redirected to login page with url as <domain>/login?to=%2Flogin instead of <domain/login?to=%2Fusers I don't know if that's an issue or expected solution but the issue is when I am logged in and try to go to /login page I get 404 page not found message. Shouldn't the user should be redirected to dashboard or other page if they are logged in?
Omer
Omer2y ago
hmm let me check Do you have a chance to provide an environment where we can reproduce this issue? You can fork here, https://refine.dev/docs/examples/authentication/headless/
like-gold
like-gold2y ago
I will create it tomorrow if that's okay?
Omer
Omer2y ago
of course!
like-gold
like-gold2y ago
Refine Headless Example (forked) - StackBlitz
Run official live example code for Refine Headless, created by Refinedev on StackBlitz
like-gold
like-gold2y ago
Hello @Omer here is the exact code environment that I am using in my code-base.
Omer
Omer2y ago
Hey @dipbazz , Thank you! Let's take a look 👀 Hey @dipbazz , We were able to reproduce the issue. The solution seems a bit complicated. Can you open a GitHub issue? 👀
like-gold
like-gold2y ago
Hey @Omer I have created an issue and here is the link for the issue https://github.com/refinedev/refine/issues/2927
GitHub
[BUG] Issue on redirect url after user is logged out on jwt refresh...
Describe the bug I am using the JWT authentication on my project and it works fine when I log in and save the access token in my local or session storage. But whenever my refresh token is expired a...
Omer
Omer2y ago
Thank you! @dipbazz
like-gold
like-gold2y ago
Hi @dipbazz, https://github.com/refinedev/refine/issues/2927 Did you see my response the issue ? If the problem persists, please don't forget to report back. 🙏
like-gold
like-gold2y ago
Hi @alicanerdurmaz do I need to update refine-core in my package?
like-gold
like-gold2y ago
After PR is merged and released, yes you need do update patch version. But, when you make the changes I wrote, I believe your problem will be solved. I tested on your reproduced code. You don't need to wait release.
like-gold
like-gold2y ago
Thank you @alicanerdurmaz for your solution but it's still not working for me. Am I doing something wrong?
like-gold
like-gold2y ago
Oh, I'm sorry. Do you want to <domain>/login?to=/users am i right ? not <domain>/login Sorry for misunderstanding, Unfortunately you have to wait for the fix release. after release, u can do that with: https://refine.dev/docs/api-reference/core/providers/auth-provider/#redirection-after-error
checkError: (error) => {
if (error.response.status === 401) {
return Promise.reject('/users')
}
return Promise.resolve()
},
checkError: (error) => {
if (error.response.status === 401) {
return Promise.reject('/users')
}
return Promise.resolve()
},
like-gold
like-gold2y ago
Yeah that's what I would like to do. But can I have that custom-redirect-url to be dynamic. For eg: I was trying to access the resource posts and my refresh token got expired then I will be logged out and when I provide my credentials then now I should be redirected to /posts page. And same for the users resources as well.
like-gold
like-gold2y ago
I understood. when you mean dynamic, it should be redirected to <current resource> We will inform you when we release. thank you for clear explanation 🙏
like-gold
like-gold2y ago
Yeah exactly.
like-gold
like-gold2y ago
@dipbazz Hi again, we released fix. after npm i @pankod/refine-core@next you can try this:
checkError: (error) => {
if (error.response.status === 401) {
return Promise.reject(location.pathname)
}
return Promise.resolve()
},
checkError: (error) => {
if (error.response.status === 401) {
return Promise.reject(location.pathname)
}
return Promise.resolve()
},
refine core has not any opinion to redirect path after checkError is rejected. because we think this is app spesific feature and may vary. let me now if anything goes wrong 🙏
like-gold
like-gold2y ago
Hey @alicanerdurmaz I will test it tomorrow and let you know if something goes wrong. And BTW can I use this @next in production or just to test if it works?
like-gold
like-gold2y ago
next tag is currently @pankod/refine-core@3.88.1 and includes only logout logic changes https://github.com/refinedev/refine/compare/master...%40pankod/refine-core%403.88.1?diff=split our tests and example repos are working fine. I don't think it will be a problem for other parts of app but, better safe than sorry 🙏 😀
like-gold
like-gold2y ago
Thank you @alicanerdurmaz it's now working as expected.
like-gold
like-gold2y ago
I'm glad to hear that 🚀