Im using Auth0 in an electron app to manage a log-in system. I referenced this tutorial here:
https://auth0.com/blog/securing-electron-applications-with-openid-connect-and-oauth-2/
to get started with using it.
Auth0 has been working great when I've been working in development but for some reason fails after I call "yarn package" to build the app. My electron app used electron-react-boilerplate (https://github.com/electron-react-boilerplate/electron-react-boilerplate).
Here are the important files:
// imports
...
import {
getAuthenticationURL,
refreshTokens,
loadTokens,
logout,
getLogOutUrl,
getProfile,
getResponse,
} from './services/authservice';
export default class AppUpdater {
constructor() {
...
}
}
let mainWindow: BrowserWindow | null = null;
if (process.env.NODE_ENV === 'production') {
const sourceMapSupport = require('source-map-support');
sourceMapSupport.install();
}
if (
process.env.NODE_ENV === 'development' ||
process.env.DEBUG_PROD === 'true'
) {
require('electron-debug')();
}
const installExtensions = async () => {
...
};
const createWindow = async () => {
console.log('now starting the main process');
if (
process.env.NODE_ENV === 'development' ||
process.env.DEBUG_PROD === 'true'
) {
await installExtensions();
}
const RESOURCES_PATH = app.isPackaged
? path.join(process.resourcesPath, 'assets')
: path.join(__dirname, '../assets');
const getAssetPath = (...paths: string[]): string => {
return path.join(RESOURCES_PATH, ...paths);
};
mainWindow = new BrowserWindow({
show: true,
width: 1024,
height: 728,
titleBarStyle: 'hidden', // add this line
frame: false,
//icon: getAssetPath('icon.png'),
webPreferences: {
nodeIntegration: true,
},
});
mainWindow.loadURL(`file://${__dirname}/index.html`);
const devtools = new BrowserWindow();
mainWindow.webContents.setDevToolsWebContents(devtools.webContents);
mainWindow.webContents.openDevTools({ mode: 'detach' });
};
/**
* Add event listeners...
*/
let win = null;
function createAuthWindow() {
destroyAuthWin();
win = new BrowserWindow({
width: 1000,
height: 600,
webPreferences: {
nodeIntegration: false,
enableRemoteModule: false,
},
});
console.log(getAuthenticationURL());
win.loadURL(getAuthenticationURL());
const {
session: { webRequest },
} = win.webContents;
const filter = {
urls: [
'file:///callback*',
],
};
webRequest.onBeforeRequest(filter, async ({ url }) => {
console.log(url);
await loadTokens(url)
.then((res) => {
console.log(res);
})
.catch(console.log);
console.log('from web request');
createWindow();
return destroyAuthWin();
});
win.on('authenticated', () => {
console.log('WE HAVE AUTHENTICATED');
destroyAuthWin();
});
win.on('closed', () => {
win = null;
});
}
function destroyAuthWin() {
if (!win) return;
win.close();
win = null;
}
// logout logic: removed for simplicity
ipcMain.on('profileRequest', (event, arg) => {
//event.reply('profileResponse', getProfile());
event.returnValue = getProfile();
});
const showWindow = async () => {
try {
await refreshTokens();
return createWindow();
} catch (err) {
createAuthWindow();
}
};
this is my auth services file:
let accessToken = null;
let profile = {};
let refreshToken = null;
export function getAccessToken() {
return accessToken;
}
export function getProfile() {
return profile;
}
export function getAuthenticationURL() {
return (
'https://' +
auth0Domain +
'/authorize?' +
'scope=openid%20profile%20offline_access&' +
'response_type=code&' +
'client_id=' +
clientId +
'&' +
'redirect_uri=' +
redirectUri
);
}
export async function refreshTokens() {
const refreshToken = await keytar.getPassword(keytarService, keytarAccount);
if (refreshToken) {
const refreshOptions = {
method: 'POST',
url: `https://${auth0Domain}/oauth/token`,
headers: { 'content-type': 'application/json' },
data: {
grant_type: 'refresh_token',
client_id: clientId,
refresh_token: refreshToken,
},
};
try {
const response = await axios(refreshOptions);
res = response;
accessToken = response.data.access_token;
profile = jwtDecode(response.data.id_token);
} catch (error) {
await logout();
throw error;
}
} else {
throw new Error('No available refresh token.');
}
}
export async function loadTokens(callbackURL) {
console.log('loading tokens:');
console.log(callbackURL);
res = callbackURL;
const urlParts = url.parse(callbackURL, true);
const query = urlParts.query;
console.log(query);
const exchangeOptions = {
grant_type: 'authorization_code',
client_id: clientId,
code: query.code,
redirect_uri: redirectUri,
};
const options = {
method: 'POST',
url: `https://${auth0Domain}/oauth/token`,
headers: {
'content-type': 'application/json',
},
data: JSON.stringify(exchangeOptions),
};
try {
const response = await axios(options);
console.log('from token:');
console.log(response);
res = response;
accessToken = response.data.access_token;
profile = jwtDecode(response.data.id_token);
refreshToken = response.data.refresh_token;
console.log(getProfile());
if (refreshToken) {
await keytar.setPassword(keytarService, keytarAccount, refreshToken);
}
} catch (error) {
await logout();
throw error;
}
}
I have a file in my components folder called "Auth.jsx" which has a "get profile" methods which interacts with the main process to get the profile
const getProfile = () => {
return ipcRenderer.sendSync('profileRequest', true);
};
After I package the electron app, the getProfile method always returns null/undefined.
Here are the auth0 logs:
Auth0 Logs
It shows that there is a successful Login and Exchange.
Finally, here's my webpack file: "webpack.config.main.prod.babel"
/**
* Webpack config for production electron main process
*/
import path from 'path';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import TerserPlugin from 'terser-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import baseConfig from './webpack.config.base';
import CheckNodeEnv from '../scripts/CheckNodeEnv';
import DeleteSourceMaps from '../scripts/DeleteSourceMaps';
import dotenv from 'dotenv';
CheckNodeEnv('production');
DeleteSourceMaps();
const devtoolsConfig =
process.env.DEBUG_PROD === 'true'
? {
devtool: 'source-map',
}
: {};
export default merge(baseConfig, {
...devtoolsConfig,
mode: 'production',
target: 'electron-main',
entry: './src/main.dev.ts',
output: {
path: path.join(__dirname, '../../'),
filename: './src/main.prod.js',
},
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode:
process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
openAnalyzer: process.env.OPEN_ANALYZER === 'true',
}),
new webpack.EnvironmentPlugin({
NODE_ENV: 'production',
DEBUG_PROD: true,
START_MINIMIZED: false,
/*
other environment variables, including auth0 domain name and clientID
*/
}),
],
/**
* Disables webpack processing of __dirname and __filename.
* If you run the bundle in node.js it falls back to these values of node.js.
* https://github.com/webpack/webpack/issues/2010
*/
node: {
__dirname: false,
__filename: false,
},
});
Im suspecting the problem might have something to do with webpack since it's only the packaged version of the application that doesn't work properly. Im not sure exactly what the problem is, whether is a problem in the code or if I need to specifically change something within my Auth0 dashboard. If you have any suggestions or any ideas on how to debug let me know!
I had the exact same issue! I fixed it by changing the import method for jwt-decode from require to import
import jwtDecode from 'jwt-decode'
Related
I'm trying to build an application using Electron with Monaco editor. I used monaco language client to integrate the editor with clangd. But somehow it didn't work... After manually connecting to the LSP server through web socket, I get the following error in the terminal:
TypeError: node.exports.StreamMessageReader is not a constructor
at createStreamConnection (/Volumes/Data/Develop/cpcode/dist/electron/main.js:3100:18)
at createProcessStreamConnection (/Volumes/Data/Develop/cpcode/dist/electron/main.js:3094:12)
at createServerProcess (/Volumes/Data/Develop/cpcode/dist/electron/main.js:3090:10)
at launch (/Volumes/Data/Develop/cpcode/dist/electron/main.js:3114:28)
at /Volumes/Data/Develop/cpcode/dist/electron/main.js:3160:13
at WebSocketServer.completeUpgrade (/Volumes/Data/Develop/cpcode/node_modules/ws/lib/websocket-server.js:431:5)
at WebSocketServer.handleUpgrade (/Volumes/Data/Develop/cpcode/node_modules/ws/lib/websocket-server.js:339:10)
at Server.<anonymous> (/Volumes/Data/Develop/cpcode/dist/electron/main.js:3147:13)
at Server.emit (node:events:526:28)
at onParserExecuteCommon (node:_http_server:727:14)
I am starting the server in Electron main process. Here's the code for it:
process.env.DIST = join(__dirname, '..')
process.env.PUBLIC = app.isPackaged ? process.env.DIST : join(process.env.DIST, '../public')
import { join } from 'path'
import { app, BrowserWindow } from 'electron'
import ws from "ws";
import http from "http";
import url from "url";
import net from "net";
import express from "express";
import * as rpc from "vscode-ws-jsonrpc";
import * as server from "vscode-ws-jsonrpc/server";
import * as lsp from "vscode-languageserver";
import { Message } from "vscode-languageserver";
export function launch(socket: rpc.IWebSocket) {
const reader = new rpc.WebSocketMessageReader(socket);
const writer = new rpc.WebSocketMessageWriter(socket);
// start the language server as an external process
const socketConnection = server.createConnection(reader, writer, () =>
socket.dispose()
);
const serverConnection = server.createServerProcess("CPP", "clangd");
if (serverConnection) {
server.forward(socketConnection, serverConnection, (message) => {
if (Message.isRequest(message)) {
console.log("server request!!!");
if (message.method === lsp.InitializeRequest.type.method) {
const initializeParams = message.params as lsp.InitializeParams;
initializeParams.processId = process.pid;
}
}
return message;
});
}
}
process.on("uncaughtException", function (err: any) {
console.error("Uncaught Exception: ", err.toString());
if (err.stack) {
console.error(err.stack);
}
});
export const startLSP = () => {
// create the express application
const app = express();
// server the static content, i.e. index.html
app.use(express.static(__dirname));
// start the server
const server = app.listen(3000);
// create the web socket
const wss = new ws.Server({
noServer: true,
perMessageDeflate: false
});
server.on(
"upgrade",
(request: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
// eslint-disable-next-line n/no-deprecated-api
const pathname = request.url
? url.parse(request.url).pathname
: undefined;
if (pathname === "/lsp") {
wss.handleUpgrade(request, socket, head, (webSocket) => {
const socket: rpc.IWebSocket = {
send: (content) =>
webSocket.send(content, (error) => {
if (error) {
throw error;
}
}),
onMessage: (cb) => webSocket.on("message", cb),
onError: (cb) => webSocket.on("error", cb),
onClose: (cb) => webSocket.on("close", cb),
dispose: () => webSocket.close()
};
// launch the server when the web socket is opened
if (webSocket.readyState === webSocket.OPEN) {
launch(socket);
} else {
webSocket.on("open", () => launch(socket));
}
});
}
}
);
};
let win: BrowserWindow | null
// Here, you can also use other preload
const preload = join(__dirname, './preload.js')
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite#2.x
const serverURL = process.env['VITE_DEV_SERVER_URL']
function createWindow() {
win = new BrowserWindow({
icon: join(process.env.PUBLIC, 'logo.svg'),
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload,
},
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'hidden',
})
startLSP();
// Test active push message to Renderer-process.
win.webContents.on('did-finish-load', () => {
win?.webContents.send('main-process-message', (new Date).toLocaleString())
})
if (app.isPackaged) {
win.loadFile(join(process.env.DIST, 'index.html'))
} else {
win.loadURL(serverURL)
}
}
app.on('window-all-closed', () => {
win = null
})
app.whenReady().then(createWindow)
I am using Vite as a bundler and I'm not sure if it has anything to do with this. Anyway, here's my vite.config.ts:
import fs from 'fs'
import path from 'path'
import { defineConfig } from 'vite'
import electron from 'vite-plugin-electron'
import renderer from 'vite-plugin-electron-renderer';
import { nodePolyfills } from 'vite-plugin-node-polyfills'
fs.rmSync('dist', { recursive: true, force: true }) // v14.14.0
export default defineConfig({
plugins: [
electron({
main: {
entry: 'electron/main.ts',
},
preload: {
input: {
// Must be use absolute path, this is the restrict of Rollup
preload: path.join(__dirname, 'electron/preload.ts'),
},
},
// Enables use of Node.js API in the Renderer-process
// https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#electron-renderervite-serve
renderer: {},
}),
nodePolyfills({
// Whether to polyfill `node:` protocol imports.
protocolImports: true,
}),
],
})
After Googling, I tried to install vscode-languageserver and vscode-languageserver-protocol, but no luck. The problem still continues.
Does anyone has a solution to this? Thanks in advance!
I keep getting this error upon starting my nestjs application:
TypeError: Cannot read property 'retryAttempts' of undefined
The error is originating from my common config file, which was loaded into my configurations in app.module. I had tried call an await function in it and it gave me the retryattempts undefined error.
However, after commenting out that line, the error is resolved.
Context: I am trying to load variables from a pcf config server in place of env variables.
getEnvfrompcfconfig
import { getPcfServerClientCredentials } from '#common/configurations/pcf-credentials.config';
import axios, { AxiosResponse } from 'axios';
import * as client from 'cloud-config-client';
import { getAppEnv } from 'cfenv';
export const getEnvFromPcfConfigServer = async () => {
const appEnv = getAppEnv();
const pcfCredentials = getPcfServerClientCredentials();
const { client_secret, client_id, access_token_uri, uri } = pcfCredentials;
const accessToken: string = await axios
.post(
access_token_uri,
new URLSearchParams({
grant_type: 'client_credentials', //gave the values directly for testing
client_id,
client_secret,
}),
)
.then((res: AxiosResponse) => {
return res.data.access_token;
})
.catch((e) => console.log('cannot get access token', e));
const options: client.Options = {
name: 'application',
rejectUnauthorized: false,
endpoint: uri,
profiles: [appEnv.app.space_name || 'dev'],
headers: { authorization: `bearer ${accessToken}` },
};
const config = await client.load(options);
const url = config.get('baseurl');
return url;
};
config file
import { ScheduleLog as ScheduleLogEntity } from '#scheduleLog/entities/schedule-log.entity';
import { CustomNamingStrategy } from '#common/helpers/database/database.helper';
import { Schedule as ScheduleEntity } from 'src/schedule/entities/schedule.entity';
import { ICommonConfiguration } from '#common/interfaces/configurations/common-configuration';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { getCreds } from '#common/configurations/credhub.config';
export const configuration = async () => {
const {
APP_PORT,
ENVIRONMENT,
MARIADB_HOST,
MARIADB_PORT,
MARIADB_USERNAME,
MARIADB_PASSWORD,
MARIADB_DATABASE,
AUTH_PASSWORD,
EUREKA_AWS_URL,
EUREKA_UAA,
EUREKA_PCF,
EUREKA_DATALOADER,
EUREKA_COMMS,
EUREKA_PROXY_HOST,
EUREKA_PROXY_PORT,
EUREKA_FULFILMENT,
} = process.env;
const creds = getCreds();
const thing = await getEnvFromPcfConfigServer(); //<-- line that is causing problem
console.log('thing', thing);
return {
APP: {
PORT: Number(APP_PORT),
ENVIRONMENT,
},
UAAPASSWORD: creds.uaaPassword || AUTH_PASSWORD,
DATABASE: {
type: 'mysql',
host: MARIADB_HOST,
port: Number(MARIADB_PORT),
username: MARIADB_USERNAME,
password: MARIADB_PASSWORD || creds.mariaDbPassword,
database: MARIADB_DATABASE,
entities: [ScheduleEntity, ScheduleLogEntity],
synchronize: false,
namingStrategy: new CustomNamingStrategy(),
// autoLoadEntities: true,
},
HTTPMODULE: {
BASEURL: {
AWS: EUREKA_AWS_URL,
UAA: EUREKA_UAA,
PCF: EUREKA_PCF,
DATALOADER: 'hi',
COMMS: EUREKA_COMMS,
FULFILMENT: EUREKA_FULFILMENT,
},
PROXYAGENT: new HttpsProxyAgent({
host: EUREKA_PROXY_HOST,
port: EUREKA_PROXY_PORT,
}),
},
};
};
App.module
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
envFilePath: `.env.stage.${appEnv.app.space_name || 'local'}`,
}),
I find this very weird. Anyone able to help?
I am trying to develop a mail sending module in my NestJs project. when I send the mail through my local machine it works fine. But when it goes to the server (digital-ocean droplet) it throws the following error and it does not send any email further. what is going on and fix this?
My code segment is below:
Auth controller
#Post('register')
async register(#Body() register: RegisterRequest) {
const res = await this.authService.register(register);
if (res instanceof Error) {
throw res;
}
return {
statusCode: 200,
message:
'Your account is registered successfully. Please check your email to verify your account',
};
}
Auth Service
async register(register: RegisterRequest): Promise<any> {
register.isRegister = true;
register.status = 0;
register.roleId = 5;
const user = await this.userService.save(register);
console.log('User Saved');
if (user instanceof User) {
if (register.isRegister) {
console.log('IS REGISTER');
// Generate Token
const resultsToken = await this.userTokenService.save(user.id, 1);
console.log('USER TOKEN SAVED');
if (resultsToken) {
this.mailService.sendUserConfirmation(user, resultsToken.token);
}
}
}
return user;
}
User Service
async save(register: RegisterRequest): Promise<User> {
try {
const token = generateToken(8);
const user = new User();
user.email = register.email;
user.firstName = register.firstName;
user.lastName = register.lastName;
user.password = hashPassword(register.password || token);
user.role = register.roleId;
user.phoneNumber = register.phoneNumber;
user.isActive = register.status;
const errors = await validate(user);
if (errors.length > 0) {
throw new ParameterMissingException(
errors[0].constraints[Object.keys(errors[0].constraints)[0]],
);
}
const existing = await this.findOneByEmail(register.email);
if (existing) {
throw new EntityFoundException('Email');
}
const registered = await this.userRepository.save(user);
return registered;
} catch (error) {
return error;
}
}
User Token service
async save(userId: number, type: number): Promise<UserToken> {
try {
const userToken = new UserToken();
userToken.user = userId;
userToken.type = type;
userToken.token = generateToken();
if (type === 2) {
userToken.expireIn = 15;
}
console.log('USER TOKEN SAVE');
return await this.userTokenRepository.save(userToken);
} catch (error) {
console.log('USER TOKEN ERROR');
return error;
}
}
Mail Service
sendUserConfirmation(user: any, token: string) {
const url = `${
this.configService.get<string>('REGISTER_CONFIRM_URL') + token
}`;
console.log('MAIL SERVICE ' + url);
this.mailerService
.sendMail({
to: user.email,
// from: '"Support Team" <support#example.com>', // override default from
subject: 'Greetings from CEWAS!',
template: join(__dirname, 'templates') + './register-confirmation',
context: {
url,
},
})
.then((r) => console.log(r))
.catch((err) => {
console.log('MAIL SERVICE ERROR');
console.log(err);
});
}
Mail Module and Config
import { MailerModule } from '#nestjs-modules/mailer';
import { HandlebarsAdapter } from '#nestjs-
modules/mailer/dist/adapters/handlebars.adapter';
import { Module } from '#nestjs/common';
import { MailService } from './mail.service';
import { join } from 'path';
#Module({
imports: [
MailerModule.forRoot({
transport: {
service: 'gmail',
secure: true,
auth: {
user: '********#gmail.com',
pass: '********',
},
},
defaults: {
from: '"<No Reply>" <*******#gmail.com>',
},
template: {
dir: join(__dirname, 'templates'),
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
},
}),
],
providers: [MailService],
exports: [MailService],
})
export class MailModule {}
Hi try whit this configuration in your transport:
transport: {
host: 'smtp.gmail.com',
port: 465,
ignoreTLS: true,
secure: true,
auth: {
user: '********#gmail.com', //use the .env variable here for security
pass: '********'
},
}
and make sure you turned on access for less secure app
here the link: less secure app
I'm having trouble on uploading files to my production server, in a local environment, everything works fine just as expected, but when I try to do a a Mutation containing file uploads (and only on those mutations) it throws me a CORS error. I'm using Spaces to upload the files and save them into my Database.
app.ts (Back-end):
const configureExpress = async () => {
const app: express.Application = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use('/services', servicesRoute);
const { typeDefs, resolvers } = await buildSchema;
const schema = makeExecutableSchema({ typeDefs, resolvers });
const server = new ApolloServer({
schema,
playground: true,
introspection: true,
uploads: {
maxFileSize: 1000000000,
maxFiles: 100,
},
context: async ({ req }) => ({
auth: await Auth.getUser(req),
}),
formatError: (err) => ({
message: err.message,
code: err.extensions && err.extensions.code,
locations: err.locations,
path: err.path,
extensions: err.extensions && err.extensions.exception,
}),
});
server.applyMiddleware({ app });
return app;
};
export default () => database.connect().then(configureExpress);
client-auth.ts (On the Front-end):
const errorLink = onError(({ graphQLErrors }: any) => {
if (graphQLErrors) {
console.log(graphQLErrors);
graphQLErrors.map(({ message }: any) => console.log(message));
graphQLErrors.map(({ code }: any) => {
if (code === 'UNAUTHENTICATED') {
persistStore(store)
.purge()
.then(() => {
window.location.reload();
});
}
return true;
});
}
});
const authLink = setContext((_, { headers }) => {
const token = store.getState().auth.user!.token;
return {
headers: {
...headers,
authorization: `Bearer ${token}`,
},
};
});
const uploadLink = createUploadLink({
uri: 'https://api.example.com.br/graphql'
// uri: 'http://localhost:4000/graphql',
});
const client = new ApolloClient({
link: ApolloLink.from([errorLink, authLink, uploadLink]),
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: 'network-only',
},
},
});
resolver.ts
return propertyModel.create({
...data
} as DocumentType<any>).then(async property => {
const user = await userModel.findById(input.userId);
if (!user) throw new UserNotFound();
await ownerModel.findByIdAndUpdate(user.owner, {
$push: {
properties: property.id,
}
});
if (input.images) {
input.images.forEach(async image => {
const uploadedImage = await FileS3.upload(image, {
path: 'images',
id: propertyId.toHexString(),
});
await property.updateOne({$push: {images: uploadedImage}});
});
}
if (input.scripture) {
const uploadedScripture = await FileS3.upload(input.scripture, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({scripture: uploadedScripture});
}
if (input.registration) {
const uploadedRegistration = await FileS3.upload(input.registration, {
path: 'documents',
id: propertyId.toHexString(),
})
await property.updateOne({
registration: uploadedRegistration,
});
};
if (input.car) {
const uploadedCar = await FileS3.upload(input.car, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
car: uploadedCar,
});
};
if (input.ccir) {
const uploadedCcir = await FileS3.upload(input.ccir, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
ccir: uploadedCcir,
});
};
if (input.itr) {
const uploadedItr = await FileS3.upload(input.itr, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
itr: uploadedItr,
});
};
if (input.subscription) {
const uploadedSubscription = await FileS3.upload(input.subscription, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
subscription: uploadedSubscription
});
return property;
});
};
I'm really lost regarding this error, is this an actual server error? (Production server is on DigitalOcean in Ubuntu) or something wrong regarding the code?
For CORS, if you are using the latest version of ApolloServer then turn on the CORS:
const server = new ApolloServer({
cors: {
credentials: true,
origin: true
},,
...
});
//also apply it here
server.applyMiddleware({ app,
cors: {
credentials: true,
origin: true
}
});
400 status code is returned for bad request which happens when a client sends a malformed request to server, You need to verify that your client is sending the correct data and headers on via correct HTTP verb (post/get etc)
If any one happens to have this same problem, here's how I solved.
After digging through the code I realized that in the server I was receiving a Maximum call stack size exceeded, upon looking further to this problem I realized that It was an error regarding the Graphql-Upload dependency, I fixed it by removing it as a dependency and added the following on my package.json:
"resolutions": {
"fs-capacitor":"^6.2.0",
"graphql-upload": "^11.0.0"
}
after this I just executed this script: npx npm-force-resolutions. And It worked all fine.
So I have a very simple Next.js application that I would like to add authentication to using next-iron-session and the withIronSession function. Inside of my api folder, I have a simple component that grabs the login form data, performs a complex authentication action, then sets the user using
req.session.set("user", { email });
import { withIronSession } from "next-iron-session";
const VALID_EMAIL = "user";
const VALID_PASSWORD = "password";
export default withIronSession(
async (req, res) => {
if (req.method === "POST") {
const { email, password } = req.body;
if (email === VALID_EMAIL && password === VALID_PASSWORD) {
req.session.set("user", { email });
await req.session.save();
console.log(req.session.get());
return res.status(201).send("");
}
return res.status(403).send("");
}
return res.status(404).send("");
},
{
cookieName: "MYSITECOOKIE",
password: '2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8'
}
);
After the user is set in the session, I return a 201, and the client redirects to my protected page, where I try to grab that user in the getServerSideProps function using the withIronSession as a wrapper.
import { withIronSession } from "next-iron-session";
export const getServerSideProps = withIronSession(
async ({ req, res }) => {
const user = req.session.get("user");
console.log(user);
if (!user) {
res.statusCode = 403;
res.end();
return { props: {} };
}
return {
props: { }
};
},
{
cookieName: 'MYSITECOOKIE',
password: "2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8"
}
);
However, even though the user is found in the API function after it is set, ({"user", { email }}), that same session object returns {} in the getServerSideProps function in my protected component, which in my case always results in a 403. Is there a way to access the user that is set in the login component in the getServerSideProps function?
Note*
I was trying to follow this article, maybe it provides more information.
https://dev.to/chrsgrrtt/easy-user-authentication-with-next-js-18oe
next-iron-session works with node version > 12. It worked for me.
import React from "react";
import { withIronSession } from "next-iron-session";
const PrivatePage = ({ user }) => (
<div>
<h1>Hello {user.email}</h1>
<p>Secret things live here...</p>
</div>
);
export const getServerSideProps = withIronSession(
async ({ req, res }) => {
const user = req.session.get("user");
if (!user) {
res.statusCode = 404;
res.end();
return { props: {} };
}
return {
props: { user }
};
},
{
cookieName: "MYSITECOOKIE",
cookieOptions: {
secure: process.env.NODE_ENV === "production" ? true : false
},
password: process.env.APPLICATION_SECRET
}
);
export default PrivatePage;
I believe that this happens only on an http origin. Are you testing on localhost or any other non secure origin? If yes, then you need to specify secure: false in the cookieOptions.
From the documentation of next-iron-session, it is set to true by default, and this means that only secure origins (typically, origins with https) will be allowed.
Consider updating cookieOptions with secure: false for development environment and secure: true for production.
Like this:
withIronSession(
async ({ req, res }) => {...},
{
cookieName: 'MYSITECOOKIE',
cookieOptions: {
secure: process.env.NODE_ENV === 'production' ? true : false
},
password: '2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8'
}
);
I faced the same problem & found a simple solution:
import { GetServerSidePropsContext } from 'next'
import { withSessionSsr } from '#/utils/index'
export const withAuth = (gssp: any) => {
return async (context: GetServerSidePropsContext) => {
const { req } = context
const user = req.session.user
if (!user) {
return {
redirect: {
destination: '/',
statusCode: 302,
},
}
}
return await gssp(context)
}
}
export const withAuthSsr = (handler: any) => withSessionSsr(withAuth(handler))
And then I use it like:
export const getServerSideProps = withAuthSsr((context: GetServerSidePropsContext) => {
return {
props: {},
}
})
My withSessionSsr function looks like:
import { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
import { IronSessionOptions } from 'iron-session'
const IRON_OPTIONS: IronSessionOptions = {
cookieName: process.env.IRON_COOKIE_NAME,
password: process.env.IRON_PASSWORD,
ttl: 60 * 2,
}
function withSessionRoute(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, IRON_OPTIONS)
}
// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
function withSessionSsr<P extends { [key: string]: unknown } = { [key: string]: unknown }>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) {
return withIronSessionSsr(handler, IRON_OPTIONS)
}
export { withSessionRoute, withSessionSsr }