Responses to the discussion on Github - Thread 3
@sanojsilvaās feedback works.
I spent a little bit of time going through this for a project Iām workin on right now, based on his notes I wrote this to make it a bit clearer: https://talke.dev/strapi-user-permissions-jwt-cookies
Hello @christopher-talke, my last message was wrong sorry! My real question is: if you store your token server-side, you have to call the server on route change to check if authenticated? Thank you so much for this article !
Thank you, I got my answer here: HttpOnly tells the browser to save the cookie without displaying it to client-side scripts : What is httponly cookie? - Latest Hacking News | Cyber Security News, Hacking Tools and Penetration Testing Courses
Hi @christopher-talke, thanks for the thorough article! I have some issues with the httpOnly cookie not being set in Auth.js from line 132. When debugging in Chrome DevTools (Network tab), the Set-Cookie looks about right, but upon checking Cookies in the DevTools Application tab, no ātokenā cookie is present. Would you have any idea why that is?
Hi @christopher-talke, thanks for the thorough article! I have some issues with the httpOnly cookie not being set in Auth.js from line 132. When debugging in Chrome DevTools (Network tab), the Set-Cookie looks about right, but upon checking Cookies in the DevTools Application tab, no ātokenā cookie is present. Would you have any idea why that is?
Can you attach a code snippit, Iāll see If I can provide some feedback!
Thanks!
Strapi is running on http://localhost:1337 and my app is running on http://localhost:3000
This snippet is from Auth.js:132
`const token = strapi.plugins[āusers-permissionsā].services.jwt.issue({
id: user.id,
});
ctx.cookies.set("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production" ? true : false,
maxAge: 1000 * 60 * 60 * 24 * 14, // 14 Day Age
domain: process.env.NODE_ENV === "development" ? "localhost" : process.env.PRODUCTION_URL,
});
ctx.send({
status: 'Authenticated',
user: sanitizeEntity(user.toJSON ? user.toJSON() : user, {
model: strapi.query('user', 'users-permissions').model,
}),
});
console.log('TOKEN COOKIE', ctx.cookies.get('token'));`
Console.log on last line says that ctx.cookies.get('token')
is undefined
Screenshot from DevTools Network tab
Screenshot from DevTools Application tab
Can you see what might be wrong?
@andreasoby you need to enable cors credentials in config/middleware.js
Hi @devskope. Thanks for replying! Iām not sure if the middleware is added in the root config folder of Strapi (./config/middleware.js
) and the content looks like this:
module.exports = () => ({ cors: true, });
It doesnāt work for me, so I bet Iām not doing this right. Could you point me in the right direction here? andreasoby170d ago
Hi @devskope. Thanks for replying! Iām not sure if the middleware is added in the root config folder of Strapi (./config/middleware.js
) and the content looks like this:
module.exports = () => ({ cors: true, });
It doesnāt work for me, so I bet Iām not doing this right. Could you point me in the right direction here?
@sanojsilvaās feedback works.
I spent a little bit of time going through this for a project Iām workin on right now, based on his notes I wrote this to make it a bit clearer: https://talke.dev/strapi-user-permissions-jwt-cookies
i have a error: TypeError: Cannot read property ālogoutā of undefined at routerChecker.
Fix:
Path ā> ./api/logout/config/routes.json ā> routes.json
Path ā> ./api/logout/controllers/Custom.js ā> Custom.js
docs: Strapiās documentation | Strapi Documentation
Hi @devskope. Thanks for replying! Iām not sure if the middleware is added in the root config folder of Strapi (./config/middleware.js
) and the content looks like this:
module.exports = () => ({ cors: true, });
It doesnāt work for me, so I bet Iām not doing this right. Could you point me in the right direction here?
i have same error. Iām checking for any fixā¦ any idea?
Hi @devskope. Thanks for replying! Iām not sure if the middleware is added in the root config folder of Strapi (./config/middleware.js
) and the content looks like this:
module.exports = () => ({ cors: true, });
It doesnāt work for me, so I bet Iām not doing this right. Could you point me in the right direction here?
i have same error. Iām checking for any fixā¦ any idea?
./config/middleware.js
module.exports = () => ({ settings: { cors: { enabled: true, credentials: true, origin: <provide-allowed-origins>
, // ex. "http://domain.com,localhost:3000"
}, }, });
you also have to include credentials on clientside
@andreasoby @pherm
@christopher-talke, when overriding the controllers in Auth.js
, the callback() function is not the only one that sets a jwt token on the response. There are 5 locations that should be modified to set the cookie.
@christopher-talke, when overriding the controllers in Auth.js
, the callback() function is not the only one that sets a jwt token on the response. There are 5 locations that should be modified to set the cookie.
thank you @devskope, can explain about 5 locations that should be modified to set the cookie? itās very appreciated
thank you @devskope, can explain about 5 locations that should be modified to set the cookie? itās very appreciated
@pherm take a look at this
note where the setAuthCookie
function is used
So far, so good. Iāve managed to set the httpOnly cookie by following the example provided by @devskope. On top of that, I found that āaccess-control-allow-originā needs to be added to the cors headers:
./config/middleware.js
module.exports = () => ({
settings: {
cors: {
enabled: true,
origin: `${process.env.ADMIN_HOST}, ${process.env.CLIENT_HOST}`,
headers: [
"Content-Type",
"Authorization",
"X-Frame-Options",
"access-control-allow-origin"
]
},
},
});
Also, when using axios and signing in the user credentials needs to be enabled:
axios({
method: 'POST',
url: `${process.env.REACT_APP_BACKEND_URL}/auth/local`,
withCredentials: true,
data: {
identifier: this.state.username,
password: this.state.password,
}
})
.then(response => {
// Handle success.
})
.catch(error => {
// Handle error.
});
Now everything seems to work great!
it return flag httpOnly in dev tools?
It does. Are you using the Auth.js file provided by @devskope ?
Yes, but in my code in dev mode donāt return from DevTools APPLICATION flag HttpOnly , but return . It is a correct behaviour?
It should return an httpOnly cookie. Can you share your Auth.js and your folder structure so I can help you debug the issue?
[
pherm](pherm Ā· GitHub)165d ago
It should return an httpOnly cookie. Can you share your Auth.js and your folder structure so I can help you debug the issue?
i fix it, thank you @andrasoby My issue: /extensions/user-permissions/config/policies/permissions.js ā remove it!
@pherm deleting /extensions/user-permissions/config/policies/permissions.js
would mean you wonāt be able to implement the logic needed to extract the token from the cookie and bind it to the auth header
have you implemented GET Request? @andreasoby
Yes, itās basically the same as the POST request specified above, but without parsing any data object. As @devskope says, you need permissions.js for handling the authentication of the request.
Example:
axios({
method: 'GET',
withCredentials: true,
url: `${process.env.REACT_APP_BACKEND_URL}/users/me`,
})
.then(response => {
// Handle success.
})
.catch(error => {
// Handle error.
});
@devskope and @andreasoby as you solved about logout user client side?
For anyone else having any issues with the logout in @devskope excellent walkthrough - https://talke.dev/strapi-user-permissions-jwt-cookies , make sure to either post some empty data if using axios (data: {}
) or switch the method in routes.json to GET.
@ScottEAdams that walkthrough is courtesy of @christopher-talke
Oops, sorry and thank you @christopher-talke !!
ms-mousa
3d ago
I know this quite old and sorry if that bings you all with emails, but just to document how I do this and I think itās actually better than modifying strapi code through extensions.
I add two middlewares: cookieSetter and cookieGetter.
So letās start here:
// ./config/middleware.js
module.exports = {
load: {
before: ['cookieGetter', 'responseTime', 'logger', 'cors', 'responses', 'gzip'],
order: [
"Define the middlewares' load order by putting their name in this array is the right order",
],
after: ['parser', 'router', 'cookieSetter'],
},
settings: {
cors: {
origin: ['http://localhost:3000', 'PROD_URL_HERE'],
},
cookieGetter: {
enabled: true
},
cookieSetter: {
enabled: true
}
},
};
Then one file for each of those middlewares:
// ./middlewares/cookieSetter/index.js
module.exports = (strapi) => {
return {
initialize() {
strapi.app.use(async (ctx, next) => {
await next();
const requestURL = ctx.request.url;
if (requestURL.startsWith('/auth/')) {
const responseCode = ctx.response.status;
if (responseCode === 200) {
const { jwt: jwtToken } = ctx.response.body;
ctx.cookies.set('token', jwtToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 1000 * 60 * 60 * 24 * 14, // 14 Day Age
// discard the domain property if in development mode to make the cookie work
...(process.env.NODE_ENV === 'production'
? { domain: process.env.PRODUCTION_URL }
: {}),
});
}
}
});
},
};
};
// ./middlewares/cookieGetter/index.js
module.exports = (strapi) => {
return {
initialize() {
strapi.app.use(async (ctx, next) => {
if (ctx.request && ctx.request.header && !ctx.request.header.authorization) {
const token = ctx.cookies.get('token');
if (token) {
ctx.request.header.authorization = `Bearer ${token}`;
}
}
await next();
});
},
};
};
Then declare a global axios instance:
import axios from 'axios';
export const Axios = axios.create({
baseURL: process.env.BACKEND_URL, // http://localhost:1337
withCredentials: true,
});