Recently deployed a site of mine, and I am wondering if this solution to allowing the Express server on Heroku to set browser cookies for my Netlify React app is safe. I found it on an ill-explained SO answer elsewhere.
User.create(req.body)
.then(userNew => {
res
.cookie(
"usertoken",
jwt.sign({ _id: userNew._id }, process.env.JWT_KEY),
{
secure: true,
sameSite: "none",
httpOnly: false,
}
)
.json({
msg: "User registration success!",
user: {
_id: userNew._id,
userName: userNew.userName,
email: userNew.email,
favs: userNew.favs,
}
});
})
.catch(err => res.status(400).json(err));
The httpOnly, secure, and sameSite options are my concern. I used to only have httpOnly set to 'true' in development with no issue, but this solution worked for me in production. Thanks!
Set httpOnly to true to prevent client-side access to the cookie
Make sure to set expiry for JWT with expiresIn option.
Set maxAge in cookie option same at that of JWT expiry.
You can track if you are in production or not with NODE_ENV environmental variable. You can set up your code in a way that you don't keep changing it during production and development.
Here is how I commonly use the cookie along with JWT
const isProd = process.env.NODE_ENV === 'production';
res.cookie(
'usertoken',
jwt.sign({ _id: userNew._id }, process.env.JWT_KEY, { expiresIn: '1d' }),
{
secure: isProd,
sameSite: isProd ? 'none' : 'lax',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000,
}
);
Related
I am authenticating my Nuxt App with Keycloak through the Auth Module for Nuxt.js. (https://auth.nuxtjs.org/)
When the access type of my Keycloak client is set to public, everything is working as intended. Now I am trying to set the access type of my Keycloak client to confidential. But it is not working. After successfully login in my Nuxt App is caught in a Redirect Loop.
Are any of my configurations wrong or did I forget something?
Here are my OAuth2 scheme configurations:
auth: {
cookie: {
options: {
HttpOnly: false,
Secure: true
}
},
strategies: {
local: false,
keycloak: {
scheme: 'oauth2',
endpoints: {
authorization: `${process.env.KEYCLOAK_HOST}/auth/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/auth`,
userInfo: `${process.env.KEYCLOAK_HOST}/auth/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/userinfo`,
token: `${process.env.KEYCLOAK_HOST}/auth/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`,
logout: `${process.env.KEYCLOAK_HOST}/auth/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/logout?redirect_uri=` +
encodeURIComponent(process.env.FRONTEND_SCHEME + '://' + process.env.FRONTEND_HOST)
},
token: {
property: 'access_token',
type: 'Bearer',
name: 'Authorization',
maxAge: 1800
},
refreshToken: {
property: 'refresh_token',
maxAge: 60 * 60 * 24 * 30
},
responseType: 'code',
grantType: 'authorization_code',
clientId: `${process.env.KEYCLOAK_CLIENT_ID}`,
clientSecret: `${process.env.KEYCLOAK_CLIENT_SECRET}`,
scope: ['openid', 'profile', 'email'],
codeChallengeMethod: 'S256',
vuex: {
namespace: 'auth'
},
redirect: {
login: '/login',
logout: '/',
callback: '/',
home: '/'
}
}
}
},
Here are my Keycloak client settings
I tried adding the clientSecret to my OAuth2 configurations but it didn't help either. Even if it helped I am asking myself if it is the correct way to have confidential data in my Nuxt App.
It's unclear which nuxt version you are using and keycloak version.
Can this demo help you?
https://github.com/Kingside88/nuxt3-primevue-starter-auth
I'm getting a cookie locally but not once I deploy to Heroku using the heroku-redis plugin. I know that Heroku is working alright because my herokup-posgres URL is working. I have been trying to figure this out for a couple days and I'm not sure what to do.
Apollo client:
const client = new ApolloClient({
uri:
process.env.NODE_ENV === "production"
? "https://podapi.herokuapp.com/graphql"
: "http://localhost:4000/graphql",
cache: new InMemoryCache(),
credentials: "include",
});
Cors:
const corsOptions = {
origin: process.env.NODE_ENV === "production"
? (process.env.VERCEL_APP as string)
: (process.env.LOCALHOST_FRONTEND as string),
credentials: true,
};
Add redis to express
const app = express();
app.set("trust proxy", 1);
const RedisStore = connectRedis(session);
const redis = new Redis();
app.use(cors(corsOptions));
app.use(
session({
name: "qid",
store: new RedisStore({
client: redis,
disableTTL: true,
url: process.env.NODE_ENV === "production" ? process.env.REDIS_URL : undefined,
disableTouch: true,
}),
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
httpOnly: true,
sameSite: "lax",
secure: process.env.NODE_ENV === "production" ,
domain: process.env.NODE_ENV === "production" ? "podapi.herokuapp.com" : "localhost",
},
saveUninitialized: false,
secret: "mySecret",
resave: false,
})
);
Things I've tried: Adding the domain in the cookie (it doesn't work with or without it); Hard coding process.env.NODE_ENV to "production" incase it wasn't actually running production; I've added many little things like credentials: true/"include"; I've since added app.set("trust proxy", 1);
Any ideas or advice would be incredibly helpful.
I recently built a simple real-time chat application with Nextjs on the frontend and Express on the backend. The frontend is deployed on vercel while the backend is deployed on heroku. When a user logs into the app, the backend generates a jwt token which is then sent via an HttpOnly cookie back to the frontend. Here is the code for said response:
const authenticate = async (req, res, next) => {
userService
.authenticate(req)
.then((user) => {
const { token, ...userInfo } = user;
res
.setHeader(
"Set-Cookie",
cookie.serialize("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV !== "development",
maxAge: 60 * 60 * 24,
sameSite: "none",
path: "/",
})
)
.status(200)
.json(userInfo);
})
.catch(next);
};
After authentication, each subsequent request to the backend is supposed to send the token to ensure the user is logged in. For example, this is the request sent to the server to get a chat between the logged in user and another user.
const getChat = async (id) => {
const identification = id;
const response = await axios.get(
`<SERVER_URL>/chats/chat/${identification}`,
{ withCredentials: true }
);
return response;
};
In development when on localhost:3000 for the frontend and localhost:4000 for the backend, everything works fine. However, when I deployed the frontend to vercel and the backend to heroku, the browser simply refuses to save the cookie! The jwt token appears in the response header after sending the authentication request, but it isn't saved to the browser. I have tried absolutely everything I can think of, including changing the cookie parameters, but I can't get it to work. I am pretty sure I have cors properly configured on the backend as well, along with the cookie-parser module:
const cors = require("cors");
const cookieParser = require("cookie-parser");
app.use(
cors({
origin: "<CLIENT_URL>",
credentials: true,
})
app.use(cookieParser());
Thanks for taking the time to read this, any help would be greatly appreciated! And my apologies if I have not elaborated enough, this is my first post here and I'm still trying to learn the proper etiquette of the site!
HttpOnly can not read or write on client-side but when the first HttpOnly send through a request other request on the same origin can access the coockies in backend but you should request in Next.js like this.
Next.js using fetch :
const req = await fetch("http://localhost:7000/api/auth/login", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Credentials": true,
},
body: JSON.stringify({
email: formData.get("email"),
password: formData.get("password"),
}),
});
const data = await req.json();
then in backend you can read the coockie through coockie-parser
server.js:
const cookieParser = require("cookie-parser");
app.use(coockieParser());
route.post('/login',(req,res) => {
if(user){
res
.cookie("access_token", newToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production" ? true : false,
})
.status(200)
.json({ ok: true, payload: data });
}
})
Now you can read this cookie in other routes but sure about the expiration time.
My NEST api works on localhost but cookies are not working on heroku.
Here is my config
app.enableCors({ origin: process.env.FRONT_END_URL, credentials: true }); // FE_URL == http://localhost:3000 (a react app)
app.set('trust proxy', 1); // I've seen people using express using this, but isn't working here
app.use((req, res, next) => {
req.connection.proxySecure = true; // If i don't do this, it'll throw an error if i'm using secure == true and sameSite == 'none'
next();
});
app.use(
sessions({
cookieName: 'FEATSession',
secret: 'ThisIsMyTopSecretWord',
duration: 24 * 60 * 60 * 1000,
activeDuration: 1000 * 60 * 5,
cookie: {
path: '/', // Tried '/' and not setting this prop too
// domain: 'feat-be.herokuapp.com', // I tried using and not using it too
sameSite: 'none',
secure: true,
httpOnly: true, // Tried true and false too
},
}),
);
Everything else is working fine, only cookies doesn't.
I've just solved myself a very similar problem today, try different syntax of setting cookie:
SameSite property invalid Cookies HTTPONLY MERN
I develop a Shopify app and I used cookies based authentication for that.
sample code
server.use(session({ secure: true, sameSite: 'none' }, server));
server.use(
createShopifyAuth({
apiKey: SHOPIFY_API_KEY,
secret: SHOPIFY_API_SECRET_KEY,
scopes: ['read_products', 'write_products', 'read_themes', 'write_themes', 'read_script_tags', 'write_script_tags'],
async afterAuth(ctx) {
const { shop, accessToken } = ctx.session;
ctx.cookies.set('shopOrigin', shop, {
httpOnly: false,
secure: true,
sameSite: 'none'
});
// await login(ctx, accessToken, shop);
// await getScriptTag(ctx, accessToken, shop);
},
}),
)
Now, my problem is that it doesn't work on some browsers for cookies problems.
How can I use createShopifyAuth for generating access token without using cookies. if I comment server.use(session({ secure: true, sameSite: 'none' }, server)); it doesn't work any more.
I think you need to check the new JWT auth for cookies less authentication into the Shopify app.
Shopify Wiki Link