I am building a platform that offers different applications, the main platform is running on http://localhost and the applications will run each on a specific subdomain, at the moment I have an application running on http://sub.localhost.
I am using Nginx and Docker to host both the platform and the application, my goal would be to authenticate on http://localhost and use the session of the platform in the applications (subdomains), I have already taken a look at every single source/similar problem but could not find a solution, some of the sources I have read are the following:
https://github.com/nextauthjs/next-auth/discussions/1299
https://github.com/nextauthjs/next-auth/issues/405
https://github.com/nextauthjs/next-auth/issues/2718
At the moment this is my .env.local on the main platform:
NODE_ENV=development
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_AUTH_URL=...
NEXTAUTH_URL=http://localhost/
NEXTAUTH_URL_INTERNAL=http://mygames:3000/
NEXTAUTH_SECRET=...
DATABASE_URL=...
NEXT_PUBLIC_API_KEY=...
NEXT_SECRET_API_KEY=...
The following is the .env.local of the application (subdomain):
NEXTAUTH_URL=http://sub.localhost/
NEXTAUTH_URL_INTERNAL=http://mygames:3000/
NEXTAUTH_SECRET=...
DATABASE_URL=...
NEXT_PUBLIC_API_KEY=...
NEXT_SECRET_API_KEY=...
The following is my [...nextauth].js for the main platform:
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { PrismaAdapter } from '#next-auth/prisma-adapter';
import prisma from '../../../lib/prisma';
import Stripe from 'stripe';
const getDomainWithoutSubdomain = url => {
const urlParts = new URL(url).hostname.split('.');
return urlParts
.slice(0)
.slice(-(urlParts.length === 4 ? 3 : 2))
.join('.');
};
const hostName = getDomainWithoutSubdomain(process.env.NEXTAUTH_URL);
console.log("HOSTNAME", hostName);
const options = {
secret: process.env.NEXTAUTH_SECRET,
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
pages: {
signIn: '/signin'
},
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
return true;
},
async redirect({ url, baseUrl }) {
return baseUrl;
},
async session({ session, user, token }) {
return { ...session, ...user };
},
async jwt({ token, user, account, profile, isNewUser }) {
return token;
}
},
cookies: {
sessionToken: {
name: process.env.NODE_ENV === 'production' ? `__Secure-next-auth.session-token` : 'next-auth.session-token',
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production' ? true: false,
domain: '.' + hostName
}
}
}
}
export default (req, res) => NextAuth(req, res, options)
When I use getSession in the subdomain application I receive a null object, what can I do to solve this? Feel free to ask for anything for more details!
Spent ages looking for a solution...
Solution:
https://github.com/nextauthjs/next-auth/discussions/4089#discussioncomment-2290660
TLDR; You cannot use localhost subdomains as intended. You must use example.com and app.example.com. To set these go to the hosts file in you system.
Follow the steps in the github post if needed
Related
I have created a custom route in Strapi v4 called "user-screens". Locally I hit it with my FE code and it returns some data as expected. However when I deploy it to Heroku and attempt to access the endpoint with code also deployed to Heroku it returns a 404. I've tailed the Heroku logs and can see that the endpoint is hit on the server side, but the logs don't give anymore info other than it returned a 404.
I am doing other non custom route api calls and these all work fine on Heroku. I am able to auth, save the token, and hit the api with the JWT token and all other endpoints return data. This is only happening on my custom route when deployed to Heroku. I've set up cors with the appropriate origins, and I am wondering if I need to add something to my policies and middlewares in the custom route. I have verified the permissions and verified the route is accessible to authenticated users in the Strapi admin.
Here is my route:
module.exports = {
routes: [
{
method: "GET",
path: "/user-screens",
handler: "user-screens.getUserScreens",
config: {
policies: [],
middlewares: [],
},
},
],
};
And my controller:
"use strict";
/**
* A set of functions called "actions" for `user-screens`
*/
module.exports = {
getUserScreens: async (ctx) => {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
}
strapi.entityService
.findMany("api::screen.screen", {
owner: user.id,
populate: ["image"],
})
.then((result) => {
ctx.send(result);
});
},
};
For anyone facing this, the answer was to change how I returned the ctx response from a 'send' to a 'return' from the controller method. I am not sure why this works locally and not on Heroku, but this fixes it:
New controller code:
module.exports = {
getUserScreens: async (ctx) => {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
}
return strapi.entityService
.findMany("api::screen.screen", {
owner: user.id,
populate: ["image"],
})
.then((result) => {
return result;
})
.catch((error) => {
return error;
});
},
};
For my project, I'm utilizing AWS Lambda and Graphql. I used apollo-server-lambda for this project. For this project, I created custom headers. And I added a simple condition to throw an error if there is no 'event.headers.authorization'. When the app is launched in a local environment, the error is thrown correctly. But the issue is that I'm not sure how I'm going to put my authorisation in if it's continuously throwing me off. I'm certain my implementation is incorrect. I'm not sure what the best method is for obtaining authorization.
It should be put like this:
.
This is my Lambda
import * as R from 'ramda';
import { AuthenticationError, ForbiddenError } from 'apollo-server-lambda';
export const authToken = (token: string) => {
if (token === 'HELLO') {
return true;
} else {
throw new AuthenticationError('No authorization header supplied');
}
};
const lambda =
(lambdaFunc: AWSLambda.Handler): AWSLambda.Handler =>
(event, context, callback) => {
const { authorization } = event.headers;
if (R.isNil(authorization))
throw new ForbiddenError('You must be authenticated'); // always thorws me error
return authToken(event.headers.authorization);
return lambdaFunc(event, context, callback);
};
export default lambda;
This is my graphql
import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core';
import { ApolloServer} from 'apollo-server-lambda';
import schema from '../graphql/schema';
import resolvers from '../resolvers';
import lambda from '../utils/lambda';
const server = new ApolloServer({
typeDefs: schema,
resolvers,
debug: false,
plugins: [ApolloServerPluginLandingPageGraphQLPlayground()],
introspection: true,
});
export default lambda(
server.createHandler({
expressGetMiddlewareOptions: {
cors: {
origin: '*',
credentials: true,
allowedHeaders: ['Content-Type', 'Origin', 'Accept', 'authorization'],
optionsSuccessStatus: 200,
maxAge: 200,
exposedHeaders: ['authorization'],
},
},
})
);
This is YAML file
functions:
graphql:
handler: src/handlers/graphql.default
events:
- http:
path: ${env:api_prefix}/graphql
method: get
cors: true
- http:
path: ${env:api_prefix}/graphql
method: post
cors: true
I am using Apollo Server and I want to publish 2 events in the row from same resolver. Both subscriptions are working fine but only if I dispatch only one event. If I try to dispatch both, second subscription resolver never gets called. If I comment out the first event dispatch second works normally.
const publishMessageNotification = async (message, me, action) => {
const notification = await models.Notification.create({
ownerId: message.userId,
messageId: message.id,
userId: me.id,
action,
});
// if I comment out this one, second pubsub.publish starts firing
pubsub.publish(EVENTS.NOTIFICATION.CREATED, {
notificationCreated: { notification },
});
const unseenNotificationsCount = await models.Notification.find({
ownerId: notification.ownerId,
isSeen: false,
}).countDocuments();
console.log('unseenNotificationsCount', unseenNotificationsCount);// logs correct value
// this one is not working if first one is present
pubsub.publish(EVENTS.NOTIFICATION.NOT_SEEN_UPDATED, {
notSeenUpdated: unseenNotificationsCount,
});
};
I am using default pubsub implementation. There are no errors in the console.
import { PubSub } from 'apollo-server';
import * as MESSAGE_EVENTS from './message';
import * as NOTIFICATION_EVENTS from './notification';
export const EVENTS = {
MESSAGE: MESSAGE_EVENTS,
NOTIFICATION: NOTIFICATION_EVENTS,
};
export default new PubSub();
Make sure, that you use pubsub from context of apollo server, for example:
Server:
const server = new ApolloServer({
schema: schemaWithMiddleware,
subscriptions: {
path: PATH,
...subscriptionOptions,
},
context: http => ({
http,
pubsub,
redisCache,
}),
engine: {
apiKey: ENGINE_API_KEY,
schemaTag: process.env.NODE_ENV,
},
playground: process.env.NODE_ENV === 'DEV',
tracing: process.env.NODE_ENV === 'DEV',
debug: process.env.NODE_ENV === 'DEV',
});
and example use in resolver, by context:
...
const Mutation = {
async createOrder(parent, { input }, context) {
...
try {
...
context.pubsub.publish(CHANNEL_NAME, {
newMessage: {
messageCount: 0,
},
participants,
});
dialog.lastMessage = `{ "orderID": ${parentID}, "text": "created" }`;
context.pubsub.publish(NOTIFICATION_CHANNEL_NAME, {
notification: { messageCount: 0, dialogID: dialog.id },
participants,
});
...
}
return result;
} catch (err) {
log.error(err);
return sendError(err);
}
},
};
...
It has been a while since this moment.
I have also been a struggle with pubsub not working problem.
and I would like to see your ApolloClient setup code.
I changed my configurations with regard to graphql version and client-side setup.
graphql version : 14.xx.xx -> 15.3.0
const client = new ApolloClient({
uri: 'http://localhost:8001/graphql',
cache: cache,
credentials: 'include',
link: ApolloLink.from([wsLink, httpLink])
});
I want you to clarify link order, especially about httpLink, if you use in your case, "HttpLink is a terminating Link.", according to Apollo official site.
At first, I used link order [httpLink, wsLink].
Therefore, pubsub.publish didn't work.
I hope this answer will help some of graphql users.
I want to use socket.io in my Nuxtjs. Is it possible?
I tried this tutorial but I am getting the following error:
These dependencies were not found:
* fs in ./node_modules/socket.io/lib/index.js
* uws in ./node_modules/engine.io/lib/server.js
The better way to play with Nuxt.js + Socket.io is to follow this official example from core-team: https://github.com/nuxt/nuxt.js/tree/dev/examples/with-sockets
Updated answer with linked example on GitHub
I would suggest to use the nuxt-socket-io module. It is really easy to set up and has a nice documentation.
I built this litte demo example and I will list the steps that I took to build it (this is even a bit more thorough than the Setup section of the npm package):
Add nuxt-socket-io dependency to your project:
yarn add nuxt-socket-io # or npm install nuxt-socket-io
(If you already have a socket.io server you can skip this part)
Add following line to your nuxt.config.js file: serverMiddleware: [ "~/serverMiddleware/socket-io-server.js" ] (Please do not mix up serverMiddleware with middleware, this are two different things)
Then, create the file ./serverMiddleware/socket-io-server.js where you can implement your socket.io server.
// This file is executed once when the server is started
// Setup a socket.io server on port 3001 that has CORS disabled
// (do not set this to port 3000 as port 3000 is where
// the nuxt dev server serves your nuxt application)
const io = require("socket.io")(3001, {
cors: {
// No CORS at all
origin: '*',
}
});
var i = 0;
// Broadcast "tick" event every second
// Or do whatever you want with io ;)
setInterval(() => {
i++;
io.emit("tick", i);
}, 1000);
// Since we are a serverMiddleware, we have to return a handler,
// even if this it does nothing
export default function (req, res, next) {
next()
}
(If you already have Vuex set up, you can skip this)
Add following empty Vuex store, i.e., create the file ./store/index.js, since the module needs Vuex set up.
export const state = () => ({})
Add nuxt-socket-io to the modules section of nuxt.config.js, this will enable socket-io client:
{
modules: [
'nuxt-socket-io',
],
// socket.io configuration
io: {
// we could have multiple sockets that we identify with names
// one of these sockets may have set "default" to true
sockets: [{
default: true, // make this the default socket
name: 'main', // give it a name that we can later use to choose this socket in the .vue file
url: 'http://localhost:3001' // URL wherever your socket IO server runs
}]
},
}
Use it in your components:
{
data() {
return {
latestTickId: 0,
};
},
mounted() {
const vm = this;
// use "main" socket defined in nuxt.config.js
vm.socket = this.$nuxtSocket({
name: "main" // select "main" socket from nuxt.config.js - we could also skip this because "main" is the default socket
});
vm.socket.on("tick", (tickId) => {
vm.latestTickId = tickId;
});
},
}
Run it with npm run dev and enjoy your tick events :)
Nuxt + socket.io
For me worked:
Create project as nodejs app (not static page);
Install socket.io npm i socket.io;
Add serverMiddleware section to nuxt.config.js:
export default {
...,
serverMiddleware: [
{path: '/ws', handler: '~/api/srv.js'},
],
}
Create middleware /app/srv.js:
const app = require('express')()
const socket = require('socket.io')
let server = null
let io = null
app.all('/init', (req, res) => {
if (!server) {
server = res.connection.server
io = socket(server)
io.on('connection', function (socket) {
console.log('Made socket connection');
socket.on('msg', msg => {
console.log('Recived: ' + msg)
setTimeout(() => {
socket.emit('msg', `Response to: ${msg}`)
}, 1000)
})
socket.on('disconnect', () => console.log('disconnected'))
})
}
res.json({ msg: 'server is set' })
})
module.exports = app
Socket.io needs server which is not created in middleware, that's why is taken from firest request to app from res.connection.server.
Create page pages/index.vue:
<template>
<div class="container">
<input v-model="msg">
<button #click="socket.emit('msg', msg)">send</button>
<br/>
<textarea v-model="resps"></textarea>
</div>
</template>
<script>
export default {
head: {
script: [
{src: 'https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js'},
],
},
data () {
return {
socket: null,
msg: 'wwJd',
resps: '',
}
},
mounted () {
this.$axios.$get('/ws/init')
.then(resp => {
this.socket = io()
this.socket.on('msg', msg => this.resps += `${msg}\n`)
})
},
}
</script>
Run it npm run dev;
Modify and enjoy :-)
I'm trying to use the webpack-dev-server proxy configuration to send api requests to an external domain and I can't seem to get it working.
Here's my config:
var path = require('path')
module.exports = {
entry: './client/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public/assets'),
publicPath: 'assets'
},
devServer: {
contentBase: 'public',
proxy:{
'/api/v1*': {
target: 'http://laravelandwebpack.demo/',
secure: false
}
}
}
}
So, anytime my app makes a request with the uri /api/v1... it should send that request to http://laravelandwebpack.demo.
In my Vue app, I'm using the vue-resource to make the requests and I'm defaulting all requests with the needed uri prefix:
var Vue = require('vue')
Vue.use(require('vue-resource'))
new Vue({
el: 'body',
http: {
root: '/api/v1', // prefix all requests with this
headers:{
test: 'testheader'
}
},
ready: function (){
this.$http({
url: 'tasks',
method: 'GET'
}).then(function (response){
console.log(response);
}, function (response){
console.error(response);
})
}
})
The URL's are being constructed correctly, but they're still pointing to localhost:8080 which is the webpack-dev-server:
I read and re-read the docs for webpack-dev-server and I can't figure out where I have it set up wrong. Any ideas?
#Linus Borg is right.
The URL's are being constructed correctly, but they're still pointing to localhost:8080 which is the webpack-dev-server:
This doesn't matter.
In my case, I want to get http://m.kugou.com/?json=true. And I am using #Vue/cli ^3.0.0-beta.15, maybe you need to modify your code according to situation.
So, here is what I did:
App.vue
axios.get('/proxy_api/?json=true').then(data => {
console.log('data', data)
})
vue.config.js
module.exports = {
devServer: {
proxy: {
// proxy all requests whose path starting with /proxy_api to http://m.kugou.com/proxy_api then remove '/proxy_api' string
'/proxy_api': {
target: 'http://m.kugou.com',
pathRewrite: {
'^/proxy_api': '/'
}
}
}
//or just change the origin to http://m.kugou.com
// proxy: 'http://m.kugou.com'
}
}
I use /proxy_api/?json=true then update it to http://m.kugou.com/?json=true by target and pathRewrite.
'/proxy_api' is used to distinguish if the url should be proxied.
Why would I use /proxy_api? Easy to distinguish.
I got the data from http://m.kugou.com/?json=true while the url in the dev-tool is http://localhost:8080/proxy_api/?json=true.
See? that doesn't matter.
I found a workaround solution for that issue. In my case I need to proxy requests to my backend for any /api/* path, so I'm bypassing any requests which does not starts with api.
Sample:
proxy: {
'*': {
target: 'http://localhost:8081',
secure: false,
rewrite: function(req) {
console.log('rewriting');
req.url = req.url.replace(/^\/api/, '');
},
bypass: function(req, res, proxyOptions) {
if (req.url.indexOf('api') !== 0) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}else{
return false;
}
}
}
}