how to generate access token on Shopify app without session - shopify

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

Related

HttpOnly cookie appears in response header but is not being saved to the browser

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.

Nuxt Auth Module c5 doesn't refresh token automatically when token expires

Version
module: 5.0.0-1624817847.21691f1
nuxt: 2.15.8
Nuxt configuration
Universal
Nuxt configuration
// Auth: https://auth.nuxtjs.org/ (v5)
auth: {
redirect: {
login: '/account/login/',
logout: '/account/login/',
callback: '/account/login/',
home: '/account/beams/'
},
strategies: {
local: {
scheme: 'refresh',
token: {
property: 'access_token',
maxAge: 120, // seconds, 2 minutes
global: true
},
refreshToken: {
property: 'refresh_token',
data: 'refresh_token',
maxAge: 1209600 // seconds, 2 weeks
},
user: {
property: 'user',
autoFetch: true
},
endpoints: {
login: { url: '/api/account/login', method: 'post', propertyName: 'token' },
refresh: { url: '/api/account/refresh', method: 'post', },
logout: { url: '/api/account/logout', method: 'post' },
user: { url: '/api/account', method: 'get' }
},
autoLogout: false
}
}
},
Additional information
Checklist
[x] I have tested with the latest Nuxt version and the issue still occurs
[x] I have tested with the latest module version and the issue still occurs
[x] I have searched the issue tracker and this issue hasn't been reported yet
Steps to reproduce
What is expected?
When a user's token expires and refresh scheme is implemented, a user shouldn't be logged out and redirected back to the login screen, the refresh token should be used to obtain a new token and the transition should be seamless allowing any authenticated route to continue to work.
What is actually happening?
In my Nuxt project with the Auth module I've implemented the refresh scheme, however, when my token expires I don't see any request in my network being made to the refresh route after my token expires and I navigate to a protected page via the auth middleware.
I expect I'm missing some simple configuration?
My current token has an expiry of 1 minute for testing, and my refresh token has an expiry of 14 days for testing.
However, when adding:
scheme: 'refresh'
refresh: { url: '/api/account/refresh', method: 'post', }
the functionality appears to not be fetching my user and automatically logging me in.
My /api/account/refresh endpoint in my API returns the following:
{
refresh_token: 'my refresh token',
token_type: 'bearer',
expired_in: 5000
}
My /api/account/login endpoint in my API returns the following:
{
access_token: 'my token',
token_type: 'bearer',
expired_in: 1000
}
What am I missing?
You need to return refresh token from /api/account/login. And then set in conf property name of it.
I have same issue with very similar comfiguration. This is my result from API (I added refresh token to the result):
{
"access_token": "XXX",
"refresh_token": "XXX",
"expired_in": 3600,
"token_type": "bearer"
}
If I inspect cookies, I can see acces token, but refresh token does not set:
I try to manually set refresh token after login, but with same result:
const result = await this.$auth.loginWith('local', {
data: this.login,
})
this.$auth.setUserToken(result.data.accessToken, result.data.refreshToken)

Safety of setting browser cookies from Express

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,
}
);

Using Auth0 with oidc-client and PKCE

I'm using Auth0 with oidc-client (latest 1.10.1).
Now what I'm trying to do is to use PKCE flow instead of the implicit one and in AuthModule I have the following config for oidc:
NgOidcClientModule.forRoot({
// prettier-ignore
oidc_config: {
authority: environment.sts.authority,
client_id: environment.sts.clientId,
redirect_uri: `${environment.appRoot}oidc-login-redirect-callback.html`,
scope: 'openid profile email',
response_type: 'code',
post_logout_redirect_uri: `${environment.appRoot}oidc-logout-redirect-callback.html`,
silent_redirect_uri: `${environment.appRoot}oidc-silent-renew-redirect-callback.html`,
accessTokenExpiringNotificationTime: 10,
automaticSilentRenew: true,
metadata: {
authorization_endpoint: `${environment.sts.authority}authorize?audience=${environment.sts.audience}`,
userinfo_endpoint: `${environment.sts.authority}userinfo`,
issuer: environment.sts.authority,
jwks_uri: `${environment.sts.authority}.well-known/jwks.json`,
// tslint:disable-next-line:max-line-length
end_session_endpoint: `${environment.sts.authority}v2/logout?returnTo=${environment.appRootEncoded + 'oidc-logout-redirect-callback.html'}&client_id=${environment.sts.clientId}`
},
userStore: (() => new WebStorageStateStore({ store: window.localStorage })) as any
}
}),
I had to change response_type value from id_token toke to code.
Another thing I read that I need to change is in the static pages:
var config = {
userStore: new Oidc.WebStorageStateStore({ store: window.localStorage }),
response_mode: 'query',
};
var mgr = new Oidc.UserManager(config);
I understood that I need to add response_mode: 'query' for Oidc.UserManager config.
All good for now, but I think that I'm missing something because I'm infinitely redirected.
Are there some extra settings I need to do in Auth0 Application?
I found the solution: downgrade to oidc-client": "^1.8.2

How does passportJS deal with session cookies?

Initial question:
I'm using passportJS in my backend, and MongoStore as the session store, with a react frontend.
What I'm trying to do is allow the user to have a persistent login session. There's already a cookie that's getting automatically set by express-session called connect.sid. However the session doesn't seem to be persistent because req.user is undefined on subsequent requests using the same cookie that was set on login. req.user keeps returning undefined which means that passport is not verifying the session cookie somehow.
According to passport's docs that shouldn't happen.
Each subsequent request will not contain credentials, but rather the
unique cookie that identifies the session. In order to support login
sessions, Passport will serialize and deserialize user instances to
and from the session.
So what I'm trying to understand is how exactly does passportJS deal with cookies sent by the client?
Can someone help me by explaining the steps that passport takes when it comes to that?
I'm also using passport-local-mongoose plugin in my mongoose model which includes built in authenticate/serialize/deserialize methods used in auth.js below.
Relevant parts of my code for reference:
app.js: (didn't include the full file so it can be clear since it's 100+ lines, if someone suggests it could be an issue of middleware order I'll include the full code)
//session and passport initialization
const sessionStore = new MongoStore({
mongooseConnection: mongoose.connection,
collection: "sessions",
});
app.use(
session({
secret: process.env.SERVER_SECRET_KEY,
resave: false,
saveUninitialized: false,
store: sessionStore,
cookie: {
path:"/",
httpOnly: true,
expires: 9999999999999999
}
})
);
app.use(passport.initialize());
app.use(passport.session());
auth.js
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const config = require("./config");
const User = require("./models/userModel");
passport.use(
new LocalStrategy({ usernameField: "userName", passwordField: "password" }, User.authenticate())
);
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
exports.userLogin = passport.authenticate("local", {
session: true,
failureRedirect: '/users/loginfailed'
});
users.js (login request which returns the new cookie if successful)
usersRouter
.route("/login")
.post(auth.userLogin, (req, res, next) => {
console.log(req.user); // returns a valid user on login
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.json({ success: true, message: "Login successful!" });
});
movies.js (separate request after login, previous cookie included in headers)
moviesRouter
.route("/top")
.get((req, res, next) => {
console.log(req.user); // undefined
//includes a mongodb query here and a corresponding server response
})
UPDATE (thanks to this answer):
I found the issue, it was as simple as removing the cookie option from the object that was passed to session().
changed to this:
app.use(
session({
secret: process.env.SERVER_SECRET_KEY,
resave: false,
saveUninitialized: false,
store: sessionStore,
})
);
But I still don't understand why Passport likes to ignore my cookie options. What if I want to use secure or disable httpOnly or anything else I may want to try with my cookies?