NextJS middleware cookies issue - node.js

I wanted to check if user has a valid token inside the cookie before accessing /api routes on my NextJS app so I created a middleware which would check that.
Somehow I am not able to extract the value from req.cookies in my middleware... I followed NextJS official docs (https://nextjs.org/docs/advanced-features/middleware). First of all TypeScript is already throwing error: Error [TypeError]: request.cookies.getAll is not a function and also Property 'value' does not exist on type 'string'. Did you mean 'valueOf'?ts(2551)
export function middleware(request: NextRequest) {
const cookie = request.cookies.get("token")?.value;
console.log(cookie);
const allCookies = request.cookies.getAll();
console.log(allCookies);
const response = NextResponse.next();
return response;
}
// See "Matching Paths" below to learn more
export const config = {
matcher: "/api/:path*",
};

You cannot read in middleware the cookie value. You can read the cookie value only server side e.g. getserversideprop

Related

How does one secure api keys on sveltekit 1.0

I am using ghost, i made an integration and i would like to hide the api key from the front-end. I do not believe i can set restrictions on the ghost cms (that would also work). And i do believe so +page.js files are run on the browser also, so im a little confused on how to achieve this?
The interal sveltekit module $env/static/private (docs) is how you use secure API keys. Sveltekit will not allow you to import this module into client code so it provides an extra layer of safety. Vite automatically loads your enviroment variables from .env files and process.env on build and injects your key into your server side bundle.
import { API_KEY } from '$env/static/private';
// Use your secret
Sveltekit has 4 modules for accessing enviroment variables
$env/static/private (covered)
$env/static/public accessiable by server and client and injected at build (docs)
$env/dynamic/private provided by your runtime adapter; only includes variables with that do not start with the your public prefix which defaults to PUBLIC_ and can only be imported by server files (docs)
$env/dynamic/public provided by your runtime adapter; only includes variables with that do start with the your public prefix which defaults to PUBLIC_ (docs)
You don't need to hide the key.
Ghost Content API Docs:
These keys are safe for use in browsers and other insecure environments, as they only ever provide access to public data.
One common way to hide your third-party API key(s) from public view is to set up proxy API routes.
The general idea is to have your client (browser) query a proxy API route that you provide/host, have that proxy route query the third-party API using your credentials (API key), and pass on the results from the third-party API back to the client.
Because the query to the third-party API takes place exclusively on the back-end, your credentials are never exposed to the client (browser) and thus not visible to the public.
In your use case, you would have to create 3 dynamic endpoint routes to replicate the structure of Ghost's API:
src/routes/api/[resource]/+server.js to match /posts/, /authors/, /tags/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
src/routes/api/[resource]/[id]/+server.js to match /posts/{id}/, /authors/{id}/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource, id } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/${id}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
src/routes/api/[resource]/slug/[slug]/+server.js to match /posts/slug/{slug}/, /authors/slug/{slug}/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource, slug } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/slug/${slug}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
Then all you have to do is call your proxy routes in place of your original third-party API routes in your app:
// very barebones example
<script>
let uri;
let data;
async function get() {
const res = await fetch(`/api/${uri}`);
data = await res.json();
}
</script>
<input name="uri" bind:value={uri} />
<button on:click={get}>GET</button>
{data}
Note that using proxy API routes will also have the additional benefit of sidestepping potential CORS issues.

Apollo server context initialization function parameter?

In Apollo server docs, Apollo server constructor run with some configurations, one of them is a context initialization function that is called with every request, and according to the docs, this function parameter is an object that get the request (req) automatically as one field of that object.
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({
authScope: getScope(req.headers.authorization)
}),
});
This is ok for me, but in one project i got from Github that creates a what's app clone, the parameter object is different which i couldn't relate, something i searched for and i couldn't find anything can relate
export const server = new ApolloServer({
schema: rootModule.schema,
context: (session: any) => {
if (session.connection) {
const req = session.connection.context.session.request;
const cookies = req.headers.cookie;
if (cookies) {
req.cookies = cookie.parse(cookies);
}
}
return rootModule.context(session);
},
});
The context function parameter is a session object, and the developer need to get the request from that session object using that verbose code:
const req = session.connection.context.session.request;
I searched about that session parameter but couldn't find anything in the docs or anywhere else
I am confused about this inconsistency, or am i missing something?
In general, the actual context object can change dependending on the apollo server configuration (see: https://www.apollographql.com/docs/apollo-server/v2/api/apollo-server#middleware-specific-context-fields)
My guess in the "WhatsApp-clone", the parameter comes from the subscription support of Apollo Server (here as in the github repo from Version 2): https://www.apollographql.com/docs/apollo-server/v2/data/subscriptions/#operation-context
For whatever reason the argument in the example project is called session it is propably that object described in the documentation.
The subscription support is added here to the example server: https://github.com/Urigo/WhatsApp-Clone-Server/blob/master/index.ts#L13

How to remove authentication for introspection query in Graphql

so may be this is very basic question so please bear with me. Let me explain what I am doing and what I really need.
EXPLANATION
I have created a graphql server by using ApolloGraphql (apollo-server-express npm module).
Here is the code snippet to give you an idea.
api.js
import express from 'express'
import rootSchema from './root-schema'
.... // some extra code
app = express.router()
app.use(jwtaAuthenticator) // --> this code authenticates Authorization header
.... // some more middleware's added
const graphQLServer = new ApolloServer({
schema: rootSchema, // --> this is root schema object
context: context => context,
introspection: true,
})
graphQLServer.applyMiddleware({ app, path: '/graphql' })
server.js
import http from 'http'
import express from 'express'
import apiRouter from './api' // --> the above file
const app = express()
app.use([some middlewares])
app.use('/', apiRouter)
....
....
export async function init () {
try {
const httpServer = http.createServer(app)
httpServer
.listen(PORT)
.on('error', (err) => { setTimeout(() => process.exit(1), 5000) })
} catch (err) {
setTimeout(() => process.exit(1), 5000)
}
console.log('Server started --- ', PORT)
}
export default app
index.js
require('babel-core')
require('babel-polyfill')
require = require('esm')(module/* , options */)
const server = require('./server.js') // --> the above file
server.init()
PROBLEM STATEMENT
I am using node index.js to start the app. So, the app is expecting Authorization header (JWT token) to be present all the times, even for the introspection query. But this is not what I want, I want that introspection query will be resolvable even without the token. So that anyone can see the documentation.
Please shed some light and please guide what is the best approach to do so. Happy coding :)
.startsWith('query Introspection') is insecure because any query can be named Introspection.
The better approach is to check the whole query.
First import graphql and prepare introspection query string:
const { parse, print, getIntrospectionQuery } = require('graphql');
// format introspection query same way as apollo tooling do
const introspectionQuery = print(parse(getIntrospectionQuery()));
Then in Apollo Server configuration check query:
context: ({ req }) => {
// allow introspection query
if (req.body.query === introspectionQuery) {
return {};
}
// continue
}
There's a ton of different ways to handle authorization in GraphQL, as illustrated in the docs:
Adding middleware for express (or some other framework like hapi or koa)
Checking for authorization inside individual resolvers
Checking for authorization inside your data models
Utilizing custom directives
Adding express middleware is great for preventing unauthorized access to your entire schema. If you want to allow unauthenticated access to some fields but not others, it's generally recommended you move your authorization logic from the framework layer to the GraphQL or data model layer using one of the methods above.
So finally I found the solution and here is what I did.
Let me first tell you that there were 2 middle-wares added on base path. Like this:
app //--> this is express.Router()
.use(jwtMw) // ---> these are middlewares
.use(otherMw)
The jwtMw is the one that checks the authentication of the user, and since even introspection query comes under this MW, it used to authenticate that as well. So, after some research I found this solution:
jwtMw.js
function addJWTMeta (req, res, next) {
// we can check for null OR undefined and all, then check for query Introspection, with better condition like with ignore case
if (req.body.query.trim().startsWith('query Introspection')) {
req.isIntrospection = true
return next()
}
...
...
// ---> extra code to do authentication of the USER based on the Authorization header
}
export default addJWTMeta
otherMw.js
function otherMw (req, res, next) {
if (req.isIntrospection) return next()
...
...
// ---> extra code to do some other context creation
}
export default otherMw
So here in jwtMw.js we are checking that if the query is Introspection just add a variable in req object and move forward, and in next middleware after the jwtMw.js whosoever wants to check for introspection query just check for that variable (isIntrospection, in this case) and if it is present and is true, please move on. We can add this code and scale to every middleware that if req.isIntrospection is there just carry on or do the actual processing otherwise.
Happy coding :)

Is there a way to track an instance in Node without passing it around everywhere?

I have a singleton logger file. When a request comes into Express, I use middleware to set the request ID.
// Relevant parts of server.js
import express from 'express';
import requestIdMiddleware from './request-id-middleware';
const app = express();
app.use(requestIdMiddleware);
--
// Relevant parts of request-id-middleware.js
const uuid = require('uuid/v4');
const { setRequestId } = require('./logger');
module.exports = function(req, res, next) {
const id = uuid();
req.id = id;
// This sets a static variable on the plain logger object
setRequestId(id);
next();
};
--
// Relevant parts of logger.js
module.exports = {
request_id: null,
setRequestId: id => {
this.request_id = id;
},
log: message => {
// sends a log out using this.request_id, which is shared by all users of the server
}
}
Using the plain object now means everyone is sharing the same value. So despite each request calling setRequestId, it means if the first request takes longer than the second, it may use the second request's ID when referencing logger's value.
It seems I would need to make the logger a class instead of a plain object, instantiate it in my request middleware, and then pass the instance through to ensure unique request IDs across multiple calls from same request. Although unlikely, hoping to find a way around needing to pass a variable down into anything I want to log from.

Feathers Authentication via POST works but socket doesn't

Here is a repo showing my latest progress, and here is my configuration. As it stands that repo now doesn't even authenticate with REST - although I think something is wrong with socket auth that needs to be looked at.
I configured feathers, was able to create a user REST-fully with Postman, and even get an auth token (I can post to /authenticate to get a token, and then verify that token - yay postman! yay REST api!).
But in the browser the story ain't so happy. I can use find to get data back, but authenticate just gives me errors.
In my googling I found this post and updated my client javascript to be this. I have also tried doing jwt auth with the token from postman, but that gives the same Missing Credentials error. Halp!
Code incoming...
app.js (only the configuration part to show order)
app.configure(configuration(path.join(__dirname, '..')))
.use(cors())
.use(helmet()) // best security practices
.use(compress())
.use(favicon(path.join(app.get('public'), 'favicon.ico')))
.use('/', feathers.static(app.get('public')))
.configure(socketio())
.configure(rest())
.configure(hooks())
.use(bodyParser.json())
.use(bodyParser.urlencoded({ extended: true }))
.configure(services) // pull in all services from services/index.js
.configure(middleware) // middleware from middleware/index.js
.hooks(appHooks)
Within services, I first add authentication, which is in its own file and that looks like this
authentication.js
const authentication = require('feathers-authentication');
const jwt = require('feathers-authentication-jwt');
const local = require('feathers-authentication-local');
const authManagement = require('feathers-authentication-management');
module.exports = function () {
const app = this;
const config = app.get('authentication');
// Set up authentication with the secret
app.configure(authentication(config));
app.configure(authManagement(config));
app.configure(jwt());
app.configure(local(config.local));
// The `authentication` service is used to create a JWT.
// The before `create` hook registers strategies that can be used
// to create a new valid JWT (e.g. local or oauth2)
app.service('authentication').hooks({
before: {
create: [
authentication.hooks.authenticate(config.strategies)
],
remove: [
authentication.hooks.authenticate('jwt')
]
}
});
};
index.html (mostly stripped, just showing relevant script)
let url = location.protocol + '//' + location.hostname +
(location.port
? ':' + location.port
: '');
const socket = io(url);
const feathersClient = feathers()
.configure(feathers.socketio(socket))
.configure(feathers.hooks())
.configure(feathers.authentication({ storage: window.localStorage }));
Here's a screen shot showing some requests in chrome debugger and postman.
When default.json is set to use 'username' as the usernameField it outputs my Windows username, 'Matt'. This is because feathers-configuration checks to see if a value is a part of the OS environment.
https://github.com/feathersjs/feathers-configuration/blob/master/src/index.js#L26
Workaround
Manually set this configuration in authentication.js, for example:
// Set up authentication with the secret
const localConfig = {
'entity': 'users',
'service': 'users',
'usernameField': 'username',
'passwordField': 'password'
};
app.configure(authentication(config));
app.configure(local(localConfig));

Resources