How to access a token in session (Shopify Access Token) - node.js

I mostly work on front-end so I'm not super familiar with NodeJS.
I'm working on a Shopify Custom App and purpose of this app is when order placed it will receive webhook request and with that request it will send some data to other API (billing application)
I built shopify app with shopify app cli and my server.js file is like this;
import "#babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "#shopify/koa-shopify-auth";
import graphQLProxy, { ApiVersion } from "#shopify/koa-shopify-graphql-proxy";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
import session from "koa-session";
const { receiveWebhook } = require("#shopify/koa-shopify-webhooks");
import * as handlers from "./handlers/index";
dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8081;
const dev = process.env.NODE_ENV !== "production";
const app = next({
dev,
});
const handle = app.getRequestHandler();
const { SHOPIFY_API_SECRET, SHOPIFY_API_KEY, SCOPES } = process.env;
app.prepare().then(() => {
const server = new Koa();
const router = new Router();
server.use(
session(
{
sameSite: "none",
secure: true,
},
server
)
);
server.keys = [SHOPIFY_API_SECRET];
server.use(
createShopifyAuth({
apiKey: SHOPIFY_API_KEY,
secret: SHOPIFY_API_SECRET,
scopes: [SCOPES],
async afterAuth(ctx) {
//Auth token and shop available in session
//Redirect to shop upon auth
const { shop, accessToken } = ctx.session;
// This accessToken is what I need on other scope
ctx.cookies.set("shopOrigin", shop, {
httpOnly: false,
secure: true,
sameSite: "none",
});
// Register Webhook
handlers.registerWebhooks(
shop,
accessToken,
"ORDERS_PAID",
"/webhooks/orders/paid",
ApiVersion.October20
);
console.log(accessToken);
ctx.redirect("/");
},
})
);
const webhook = receiveWebhook({ secret: SHOPIFY_API_SECRET });
router.post("/webhooks/orders/paid", webhook, (ctx) => {
let user_id = ctx.state.webhook.payload.customer.id;
console.log("received webhook, user_id: ", user_id);
//console.log("ctx", ctx);
// I need that accessToken here to get some more info from Admin API with GraphQL
let accessToken = "???"
handlers
.graphqlRequest(
accessToken,
"https://my-store.myshopify.com/admin/api/2020-10/graphql.json",
`{
customer(id: "gid://shopify/Customer/${user_id}") {
email
metafields(first: 5) {
edges {
node {
key
value
}
}
}
}
}`
)
.then((res) => {
console.log("res => ", res);
})
.catch((err) => {
console.log("err => ", err);
});
});
server.use(
graphQLProxy({
version: ApiVersion.October20,
})
);
router.get("(.*)", verifyRequest(), async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
});
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
createShopifyAuth() method gets an accessToken with my app secret and app api key and I can use it in afterAuth() method but I also need that token in router.post() method to get more info from Shopify Admin API.
According to the Shopify docs(or what I understand), that key is available in session but how can I access that session data? or what can I use that token in router.push()?

The session is created only for the user who logged in the app from the admin panel. This is true for the online method for creating an access token.
If you are requesting the access token from a webhook request (a.k.a a request that doesn't require you to relogin in the app) you will not be able to access the session and you will not be able to get the access token. In addition the session expires at some point.
In order to use the Access Token in a webhook request you need to create an offline access token which is valid indefinitely. createShopifyAuth has an option for creating an offline access token, you just need to add accessMode: 'offline' to your request (more on this here)
Example:
createShopifyAuth({
apiKey: SHOPIFY_API_KEY,
secret: SHOPIFY_API_SECRET_KEY,
accessMode: 'offline',
scopes: ['read_products', 'read_orders'],
After you create the Offline Access Token you need to save that in a database (or some other way) and request it from the webhook route in order make your graphql request.
That's pretty much the just of it.

Related

Authentication using Node.js OauthClient "auth-code" flow

I'm building a SaaS application which require read-access to a user's google calendar. After the user gives consent to access their calendar during the first sign-in, I want the application to be able to authorize itself to access any of the user's calendars at any time without having to prompt the user for authorization again.
Currently I'm trying to create an authentication flow following the '#react-oauth/google' node library (specifically the "authorization code flow" here: https://react-oauth.vercel.app/). In my frontend, I get a code from the user, which is sent and successfully received by my backend. The backend is then supposed to use the code to get an access token and a refresh token for that user, but the request to exchange the code for the access token (oAuth2Client.getToken(req.body.code);) is failing with error 401 (unauthorized client.)
My ultimate goal is to store the access token and refresh token in a database somewhere so that I can access that user's calendar later at any time.
If I treat the backend as an Oath Client on google cloud and pass in the credentials for that, I get error 401 - unauthorized client, but I've given it access to the calendar api on google console, as you can see in the image:
How can I resolve the issue that I'm facing?
I started reading about service accounts that can do this for you but I'm unsure how to proceed. I saw that they can do domain wide delegation but my users will be signing in from their personal gmail accounts so that option is not applicable for me.
Frontend Code:
import { useGoogleLogin } from '#react-oauth/google';
import axios from 'axios';
export const LoginModal = () => {
const googleLogin = useGoogleLogin({
flow: "auth-code",
onSuccess: async codeResponse => {
console.log(codeResponse);
const tokens = await axios.post("http://localhost:3001/auth/google/", {
code: codeResponse.code
});
console.log(tokens);
}
})
return (<>
...some html code
<button onClick={() => { googleLogin() }}>
..some more html code
</>)
}
Backend Code:
require('dotenv').config();
const express = require('express');
const {
OAuth2Client,
} = require('google-auth-library');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
const CLIENT_ID = "xxx";
const CLIENT_SECRET = "xxx";
// initialize oathclient
const oAuth2Client = new OAuth2Client(
CLIENT_ID,
CLIENT_SECRET,
'postmessage',
);
// get token from code given from frontend
app.post('/auth/google', async (req, res) => {
console.log(req.body.code)
const { tokens } = await oAuth2Client.getToken(req.body.code); // exchange code for tokens
res.json(tokens);
});
app.post('/auth/google/refresh-token', async (req, res) => {
const user = new UserRefreshClient(
CLIENT_ID,
CLIENT_SECRET,
req.body.refreshToken,
);
const { credentials } = await user.refreshAccessToken(); // obtain new tokens
res.json(credentials);
})
app.listen(3001, () => {
console.log(`server is running`)
});
I figured it out. Basically, I was putting in the wrong clientid/client secret in my google console because I thought the frontend and backend needed different oauth client IDs. I used the same oauth client secret/id for frontend and backend and made sure to follow the answers here:
https://github.com/MomenSherif/react-oauth/issues/12
MAKE SURE TO PUT "postmessage" AS YOUR REDIRECT_URI! It will not work without that.
Working code:
frontend is the same
backend:
require('dotenv').config();
const express = require('express');
const {
OAuth2Client,
} = require('google-auth-library');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
const CLIENT_ID = XXX
const CLIENT_SECRET = XXX
// initialize oathclient
const oAuth2Client = new OAuth2Client(
CLIENT_ID,
CLIENT_SECRET,
'postmessage',
);
// get token from code given from frontend
app.post('/auth/google', async (req, res) => {
console.log("got request!")
console.log(req.body.code)
const { tokens } = await oAuth2Client.getToken(req.body.code); // exchange code for token
res.json(tokens);
});
app.listen(3001, () => {
console.log(`server is running`)
});

Internal Server Error 500 from API when deploying front-end React.js backend Express app

For my senior capstone, my group and I have developed a web-based application to simulate Bitcoin - using react.js for the front-end and node.js/express for the back-end. Up until recently, we've had all of simulation-creating-code (javascript files) inside the src directory, meaning it was being built client-side. Due to high waiting times to create a simulation from all the hashing necessary in transactions, we decided that our simulation-creating-code would be better suited for the back-end rather than the front end. Taking the load off the client and putting it on the server drastically improved the speed of creating a simulation, so 'Great success!'.
When we made this change, we ended up having some issues with require and import statements. Reactjs only supports import statements and Express uses require statements. We had to use some js functions that we developed in our API's so we imported them with require statements, and we thought we thought it was resolved because on our development environment, everything runs as smooth as butter, but once it's deployed, our login page is unable to make an API call. The error is: Failed to load resource: the server responded with a status of 500 (Internal Server Error).
It's interesting because this route in the API worked prior to making this big switch from require to import, and those changes were in other files/routes. The login API remains completely unchanged.
Either way, I'll drop some code in case it's helpful in troubleshooting.
server.js
const express = require("express");
const app = express();
const router = express.Router();
const path = require("path");
var cors = require("cors");
require("dotenv").config();
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ limit: "50mb" }));
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
// List of routes
router.use("/api/users", require("./api/users"));
router.use("/api/data", require("./api/data"));
router.use("/api/share", require("./api/share"));
router.use("/api/addresses", require("./api/addresses"));
const root = path.join(__dirname, "client/build");
app.use(express.static(root));
app.use(router);
app.use(cors({ origin: true, credentials: true }));
app.listen(
process.env.PORT,
() => `Server running on port ${process.env.PORT}`
);
api/users.js login route
const express = require("express");
const app = express();
const db = require("../dbConn");
const bcrypt = require("bcrypt-nodejs");
const cors = require("cors");
const router = express.Router();
const jwt = require("jwt-simple");
const config = require("../configuration/config.json");
// to parse JSON
app.use(express.json());
router.post("/login", (req, res) => {
//check if email and password are sent
if (!req.body.email || !req.body.password) {
return res.status(401).json({ error: "Missing username and/or password" });
}
// go into mysql and get info
let qry = `select * from user where email = "${req.body.email}"`;
db.query(qry, (err, rows) => {
if (err) {
return res.status(500).json({ error: err });
}
// assert: no error - process the result set
if (rows.length == 0) {
// no users found
res.status(400).json({ msg: "No users found" });
} else {
// process the user records
let users = [];
rows.forEach((row) => {
let user = {
uid: row.uid,
email: row.email,
role: row.role,
dateCreated: row.created_date,
password: row.password,
};
users.push(user);
});
if (users[0]) {
// Does given password hash match the database password hash?
bcrypt.compare(req.body.password, users[0].password, (err, result) => {
// Send back a token that contains the user's username
const token = jwt.encode({ email: req.body.email }, config.secret);
if (result == true) {
res.status(200).json({
msg: "user authenticated",
fname: users[0].fname,
lname: users[0].lname,
role: users[0].role,
token: token,
});
} else {
res.sendStatus(401);
}
});
}
}
});
});
router.post("/auth", cors(), (req, res) => {
try {
let user = jwt.decode(req.body.token, config.secret);
res.status(200).send(user);
} catch (err) {
res.sendStatus(401);
}
});
SignIn.js client/src/components. This is wrapped in a react.useEffect() arrow function, but again I don't believe the issue is here because this page remains unchanged from a working version.
const handleSubmit = (e) => {
e.preventDefault();
const credentials = { email, password };
// API call to login to account
// if successful, redirect to landing page
// if not, display error message
fetch(`http://${process.env.REACT_APP_API_URL}/api/users/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(credentials),
})
.then(async (res) => {
if (res.status == 200) {
return res.json();
} else {
throw new Error("Failed to Login!");
}
})
.then(async (res) => {
// Store token in cookie
setCookie("token", res.token, { path: "/${path}", maxAge: 3600 * 24 });
// Toggle state of sign in
toggleSignIn();
// Feedback
setFeedback(true);
setFeedbackObj({ message: "Signed in!", severity: "success" });
//redirect
history.push(`${process.env.PUBLIC_URL}/simulation`);
})
.catch(async (err) => {
// Feedback
setFeedback(true);
setFeedbackObj({ message: "Sign In Error", severity: "error" });
console.error(err);
});
};
If there are any other files that are of interest please let me know.
I've tried to mess with the proxy in package.json, but I don't think thats the answer because it was working previously. I've had a really difficult time finding others with similar issues or resources other than how to build a simple app with Express backend and React.js front end. This is not our issue because our application was working perfectly before this big switch. I believe the issue is stemming from require statements in our API and the running of JS functions in the API. I have no way to confirm this because in production (deployment), the errors are super uninformative, and in development, it runs perfectly fine.
I have been trying to solve this issue for a couple of weeks now, and I've made very little progress. If anyone has suggestions or tips on troubleshooting deployment, I would greatly appreciate it.
Thanks!

UnauthorizedError: invalid algorithm - Node.js express-jwt

I', trying to authenticate a user, I can create a user and get the bearer but after that I added this code to authenticate and it keeps showing the following error once every few seconds:
UnauthorizedError: invalid algorithm
at /mnt/c/Projects/myProject/node_modules/express-jwt/lib/index.js:105:22
at /mnt/c/Projects/myProject/node_modules/jsonwebtoken/verify.js:121:14
at getSecret (/mnt/c/Projects/myProject/node_modules/jsonwebtoken/verify.js:90:14)
at Object.module.exports [as verify] (/mnt/c/Projects/myProject/node_modules/jsonwebtoken/verify.js:94:10)
at verifyToken (/mnt/c/Projects/myProject/node_modules/express-jwt/lib/index.js:103:13)
at fn (/mnt/c/Projects/myProject/node_modules/async/lib/async.js:746:34)
at /mnt/c/Projects/myProject/node_modules/async/lib/async.js:1213:16
at /mnt/c/Projects/myProject/node_modules/async/lib/async.js:166:37
at /mnt/c/Projects/myProject/node_modules/async/lib/async.js:706:43
at /mnt/c/Projects/myProject/node_modules/async/lib/async.js:167:37
The code:
const express = require("express");
const { ApolloServer } = require("apollo-server-express");
const jwt = require("express-jwt");
const typeDefs = require("./settings/schema");
const resolvers = require("./settings/resolvers");
const JWT_SECRET = require("./settings/constants");
const app = express();
const auth = jwt({
secret: JWT_SECRET,
credentialsRequired: false,
algorithms: ['RS256'],
});
app.use(auth);
const server = new ApolloServer({
typeDefs,
resolvers,
playground: {
endpoint: "/graphql",
},
context: ({ req }) => {
const user = req.headers.user
? JSON.parse(req.headers.user)
: req.user
? req.user
: null;
return { user };
},
});
server.applyMiddleware({ app });
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log("The server started on port " + PORT);
});
Can't figure out why 'RS256' is not a valid algorithm, should I need to require something else? Do I need different algorithms for different tasks?
constants.js contains the following:
const JWT_SECRET = "sdlkfoish23##$dfdsknj23SD";
module.exports = JWT_SECRET;
Thanks
EDIT:
I'm not using Auth0, OAuth or any other service, I want to authenticate users by my own here
I'm registering a key when a new user is added to the DB (postgres) through the GraphQL API:
mutation {
register(login: "john", password: "doe")
}
answers with:
{
"data": {
"register": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NiwibG9naW4iOiJqb2VsIiwiaWF0IjoxNjE0NDM0NzMwLCJleHAiOjE2MTQ0MzQ5MTB9.ALltmClvlzxDJJ2FgZcFzstDUP5CY1xRzs8yQwheEn8"
}
}
then I use this bearer like that:
// Headers
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NiwibG9naW4iOiJqb2VsIiwiaWF0IjoxNjE0NDM0NzMwLCJleHAiOjE2MTQ0MzQ5MTB9.ALltmClvlzxDJJ2FgZcFzstDUP5CY1xRzs8yQwheEn8"
}
// Query
query {
current {
id,
login
}
}
I'm receiving this answer (also don't know why):
{
"error": "Unexpected token < in JSON at position 0"
}
And the error at the top of this post on the terminal
For a bearer token with JWT_SECRET, use the HS256 algorithm. The RSA256 algorithm requires a public key and private key pair.
The following code snippet works:
const auth = jwt({
secret: JWT_SECRET,
credentialsRequired: false,
algorithms: ['HS256']
});

ExpressGraphql JWT authentication, not working

For the past 2 weeks, I'm working on this solution but no success. Can anyone suggest to me where I'm going wrong? For authentication, I'm using express-graphql, express-jwt for authentication [backend-[node, express-graphql, express-jwt, graphql-tools], frontend-[React-hooks,graphql-hooks]]. Following I'm using for authentication
const authMiddleware = jwt({
secret: app.get("getsecretval"),
credentialsRequired: false,
getToken: function fromHeaderOrQuerystring(req) {
if (
req.headers.authorization &&
req.headers.authorization.split(" ")[0] === "Bearer"
) {
return req.headers.authorization.split(" ")[1];
} else if (req.query && req.query.token) {
return req.query.token;
}
return null;
}
});
app.use(authMiddleware);
app.use(
"/graphqlAPIRoute",
bodyParser.json(),
authMiddleware,
ExpressGraphQLHTTP(req => ({
schema: Schema,
rootValue: global,
context: {
user: req.user
}
}))
);
// Schema - place above authMiddleware
This even works when authorization headers not present,i.e., in case if the app idle in logged-in state as the token is stored in local storage, and not passed in headers yet the server code executes and fetches the data. Which must not be the case and must throw authentication error. If I add jwt verify we are not able to log in as there are no headers.
I suppose the auth middleware is not working and where do I place the jwt-verify function as to verify the token. For Jwt verify token I'm using
const jwtverify = require('jsonwebtoken');
Coz in express-jwt I've found no such functionality
Can anyone please lemme know where I'm going wrong? Any help would be appreciated.
Would this not work?
index.js - code sequence matters
const authMiddleware = jwt({
secret: "place secret here either pass as env",
credentialsRequired: false,
)}
app.use(authMiddleware);
const context = async (req) => {
const { authorization: token } = req.headers;
return { token };
};
app.use(
"/graphqlAPIRoute",
bodyParser.json(),
authMiddleware,
ExpressGraphQLHTTP(req => ({
schema: Schema,
rootValue: global,
}))
context: () => context(req),
);

how to implement social logins using jwt in node.js backEnd and angular 2 frontEnd

how to implement the social login such as facebook and google authentication
using Json Web token (jwt) ,same as Auth0 has implemented. i am using node js as back end and angular 2 as front end.
Perhaps I'm confused by your phrasing but facebook, google, and JWT are distinct, individual authentication strategies.
But check out passport.js it offers hundreds of authentication strategies:
defined strategy: (this example uses mongoose)
const jwtOptions = {
jwtFromRequest: PJWT.ExtractJwt.fromAuthHeader(),
secretOrKey: SECRET, // externally defined private encryption key
}
export const jwtStrategy = new PJWT.Strategy(jwtOptions,
function (jwt_payload, done) {
MongoUser.findOne({ email: jwt_payload.email }, function (err, user) { // presumes the user's email is in the jwt
if (err) return done(err, false);
if (user) return done(null, user);
else return done(null, false);
});
});
also these are needed by passport:
type Done = (err: any, user?: {}) => void;
export const serializeMongoUser = function (user: UserDoc, done: Done) {
done(null, user._id);
}
export const deserializeMongoUser = function (_id: any, done: Done) {
MongoUser.findById(_id, function (err, user) {
done(err, user);
});
}
add the middleware to your server:
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(ppt.serializeMongoUser);
passport.deserializeUser(ppt.deserializeMongoUser);
passport.use(ppt.jwtStrategy);
authenticate from a service in your app:
authenticate(): Observable<any> {
if (!this.security.jwt) return Observable.of({sucess: false})
return this.http.get('/api/authenticate', authHeaders(this.security))
.map(res => res.json());
}
call this function when you need to authenticate:
*note the authHeaders(this.security)
this.security is a subscription to the JWT token in my ngrx store, but you could also put it in localStorage.
export function authHeaders(security: Security) {
const headers = new Headers();
headers.append('Content-Type', 'application/json; charset=utf-8');
headers.append('Authorization', `JWT ${security.jwt}`);
return { headers };
}
calling authenticate() will return {success: true} or {success: false}
Here is a little example I created to show the basics of socket.io security with JWT authentication. The backend is nodeJS, socket.io, and express.
In the long run you should use passport for user sessions but the below is good to see so you get the whole picture.
Overview
When browsing to http://127.0.0.1:3001, the express middleware socketAuthenticated will create a jwt based on the hard coated user credentials (should use passport sessions for real user info).
This token is then passed to the server EJS view and used to establish a socket connection.
Create a new folder for the app and run npm install express socketio-jwt jsonwebtoken ejs-locals socket.io
Create a "public" folder within the root folder and put the socket.io-client JS file inside (you can run bower install socket.io-client to get the js file)
Create app.js file within the root folder with the following:
let express = require('express'),
socketioJwt = require('socketio-jwt'),
jwt = require('jsonwebtoken'),
port = 3001
engine = require('ejs-locals');
let app = express();
let socketAuthenticated = (req, res, next) => {
let token = jwt.sign({firstName:"jason", lastName:"owl"}, "secret", { expiresIn: 60*5 }); // assuming you have some user object in req
res.token = token; // front end will use this token to authenticate its socket connection
next();
};
app.engine('ejs', engine);
app.set('view engine', 'ejs');
app.set('view options', {defaultLayout: 'layout'});
app.use(express.static('public'));
app.use(socketAuthenticated);
app.route('/')
.get((req, res) => {
console.log(`Server Passing Token: ${res.token} to client`);
res.render('index', { jwt: res.token });
});
let server = require('http').Server(app);
let io = require('socket.io')(server);
// Setup socket server to use JWT
io.set('authorization', socketioJwt.authorize({
secret: "secret",
handshake: true
}));
io.of('/default').on('connection', (socket) => {
socket.on('data', (newConfig) => {
console.log('Data Event Recieved!');
});
});
server.listen(port);
console.log(`\Application: ${process.pid} Listening On Port: ${port}`);
4) Create a new folder called views and create a file called index.ejs:
<script type="text/javascript" src="/socket.io.js"></script>
<script>
var jwttoken = <%- JSON.stringify(jwt) %>;
function connect_socket (token) {
var socket = io.connect('/default', {
query: 'token=' + token
});
return socket;
}
var socket = connect_socket(jwttoken);
socket.emit('data');
</script>
run node app.js and open the browser to localhost:3001.

Resources