Closed johnsonthedev closed 4 years ago
Hey!
You can achieve that with getContext
:
const express = require('express')
const wildcard = require('@wildcard-api/server/express')
const app = express()
// We install the Wildcard middleware
app.use(wildcard(getContext))
// `getContext` is called on every API request. It defines the `context` object.
// `req` is Express' request object
async function getContext(req) {
const authToken = req.get("Authorization")
let user = authToken ? await tradeTokenForUser(authToken) : null
const context = {user}
return context
}
You can think of the context object as bridge between Express and your Wildcard functions.
The getContext
function is called on every API request and you can use it to provide any information you want to your Wildcard functions.
Does that make sense?
Hi! Sry, I didn't express myself well.
I got the backend piece working but struggle to attach my authorization header to all endpoint requests.
Basically, I am looking for a way to attach the token to wildcard to make all requests have that token as an Authorization header without having to manually attach it to every request in the action.
I haven't worked with RPC before. So it could be that I am missing an option that can do this for me. With axios I used an interceptor to attach it to all requests:
axios.interceptors.request.use(function (config) {
const token = store.getState().session.token;
config.headers.Authorization = token;
return config;
});
In graphql I added it to the httpLink
const httpLink = createHttpLink({ uri: 'http://localhost:3000/graphql' })
const authLink = setContext((_, { headers }) => {
const token = Cookies.get('token')
return {
headers: {
...headers,
authorization: `Bearer ${token}`
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
})
I am looking to do something similar with wildcard.
Here is a small app I created to make it more visual for you:
Backend:
endpoints.js
import service from './service'
const {endpoints} = require('@wildcard-api/server');
const jwt = require('jsonwebtoken');
const jwtSecret = "mysuperdupersecret";
endpoints.me = function() {
return this.user
};
endpoints.login = function(params) {
const user = service.login(params)
if (user) {
const token = jwt.sign({ user }, jwtSecret, { expiresIn: 60 }) // 1 min token
return token
}
return null
};
index.js
const express = require('express')
const wildcard = require('@wildcard-api/server/express')
const jwt = require('jsonwebtoken');
const jwtSecret = "mysuperdupersecret";
const app = express()
const port = 8000
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*")
res.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS")
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization")
if (req.method === "OPTIONS") {
return res.sendStatus(200)
}
next()
})
app.use(wildcard(getContext))
app.listen(port)
async function getContext(req) {
const authToken = req.get("Authorization")
let user
if (authToken) {
user = jwt.verify(token, jwtSecret);
}
return {user}
}
frontend
wildcard.js
import wildcardClient, { endpoints } from "@wildcard-api/client";
wildcardClient.serverUrl = "http://localhost:8000";
export default { wildcardClient, endpoints };
index.js
import React, { useState, useEffect } from 'react';
import { Cookies } from 'react-cookie';
import {endpoints} from '../wildcard';
const cookies = new Cookies();
export default function Index() {
const [user, setUser] = useState(0);
const loadUser = async () => {
setUser(await endpoints.me() || 'Guest')
}
useEffect(() => {
loadUser()
}, []);
const login = async () => {
const token = await endpoints.login({email:'my@email.de', password:'password'})
if (token) {
cookies.set('token', token);
loadUser()
}
}
return (
<div>
<p>Hello {user} </p>
<button onClick={login}>Login</button>
</div>
);
}
Hope this makes more sense to you
Wildcard doesn't have any utility to set cookies.
To process login and logout you should bypass Wildcard and use Express directly.
Or you can set cookies in the browser, which is what you are doing, but this is unusual and I'd stick to the more traditional approach of using Express directly, unless you have a good reason to deviate from the traditional approach.
Once the cookies are set you use getContext
to bridge over to Wildcard.
As usual, let me know if that makes sense :-).
Btw. I'm exploring ways for allowing Wildcard users to modify the context object in a protocol-agnostic and secure way.
const {endpoints, setPrivateKey} = require('@wildcard-api/server');
endpoints.login = function(params) {
const user = service.login(params)
if (user) {
// We modify the context!
this.user = user;
}
};
endpoints.getAllTrelloCards = function() {
const {user} = this;
// SQL/ORM query to get all user's Trello cards
};
// The context is secure.
// Which means you can do `this.user = {username: 'brillout'};` and
// safely assume that the user is indeed `brillout`.
// (That is, the client cannot modify the context.)
// To secure the context, Wildcard uses a private key that you provide.
setPrivateKey('AvnrrbAZ7pxH6P0s38');
// That's it!
// - No need for `getContext`.
// - Entirely transport protocol agnostic. No need to fiddle around with
// HTTP headers, cookies, or localStorage.
// - Wildcard automatically makes your context secure.
// - Cookies are going to be deprecated soon, this is future-proof.
What do you think?
Ok, I only know the REST and GraphQl way with attaching the JWT token as an Authorization Header to each request.
Especially with SSR like Next.js / Nuxt.js; That is why I am sticking to JWT.
I can't think of any other solution dealing with authorizations but if you find one I am happy to test it out :-)
I'll show you once I've implemented the new context design. I believe you'll love it ;-).
Let me know if you are stuck btw. Your solution of setting the cookie on the browser side works for you now, correct?
kind of. I am just sending the auth token as first parameter to all of my endpoints. It's a step backwards compared to REST/GraphQL, but it works for now.
Anyway, I decided to work on frontend first and wait what you come up with. Still can't really imagine it :-D
Hey @brillout ,
Following up on our twitter conversation I created this snippet. It's of course not production ready and also my first steps with React.. So please bare with me :-D
But I feel it shows what I am doing.
I am always sending the token as first argument to all my endpoints. Before running the endpoint on the server I call an intercepter to remove the token argument and forward the rest to the endpoint.
If you have a better idea to do authentication with node.js and next.js, let me know. Important
you have to add this line to the RunEndpoint function in the WildcardApi file in the node_modules repo.. I know it is super evil... again, just for showing.
[endpointName, endpointArgs, context, isDirectCall] = await require('../../../context')(endpointName, endpointArgs, context, isDirectCall)
I will have a look at it tmrw :)
I had a look at it and I'll reply tmrw ;-).
In general authorization cookies should be handled only by the server.
In your case it is only Express (or whatever server library Next.js uses) that should modify and read the cookie. You don't have to do this yourself and you can use a library instead, there are many Express auth libraries. Social login libraries usually handle this for you.
Do you really need SSR? If you don't need SSR then I'd recommend to not use Next.js but Parcel instead.
If you do need SSR then you'll need to re-bind the cookie header while doing SSR, see https://github.com/reframejs/wildcard-api/blob/master/docs/ssr-auth.md#ssr--authentication
Does that point you to the right direction?
Wildcard doesn't have any utility to set cookies.
To process login and logout you should bypass Wildcard and use Express directly.
I'm sharing a helper function via getContext that sets a cookie. What do you think of this approach? @brillout
The getContext
middleware function does not have the second parameter response
, but you still can use request.res?.cookie
. It feels a bit hacky though to use.
Hi @michie1,
Yes exactly, getContext
is not meant to be used to manipulate the response.
Auth is usually done outside of Wildcard and is done by the server framework instead. For example:
const express = require("express");
const cookieParser = require("cookie-parser");
const computeJwtToken = require('./path/to/computeJwtToken');
const app = express();
app.use(cookieParser());
app.get("/login", (req, res) => {
const options = {
maxAge: 7 * 24 * 60 * 60 * 1000, // Expires after 7 days
httpOnly: true, // The cookie is only accessible by the web server
};
const jwt = computeJwtToken(req);;
res.cookie("Authorization", jwt, options);
res.send("login successfull");
});
You can then read the Authorization
cookie in Wildcard's getContext
.
I've plans to implement a new API setContext
which I'll implement if the demand for it continuous to increase.
In the meantime if misusing getContext
to manipulate the response works for you, then go for it. As far as I can see, there shouldn't be any problem.
Let me know if you have other questions!
Some news about this at https://github.com/reframejs/wildcard-api/issues/59.
Hi, in graphql I have a cookie with my apollo-token and check for it in my context.
You wrote that we can setup a middleware in express for reading the token:
app.use(wildcard(getContext));
This makes sense for me but I am note sure where I have to set my token in the wildcard client to send it with each endpoint query. Can you help here ? Thx!!