Ooth integration with Create-React-App and Apollo GraphQL

Well, this has been exciting! My last article drafting the architecture of an extensible authentication / accounts microservice for node has raised some interest, and motivated me to explore the problem further.

A week later, the ooth project has taken more shape, and I can happily announce a first working proof-of-concept: an integration between ooth and create-react-app.

How it works

This example is comprised of 3 parts:

  1. An ooth microservice running on port 3001
  2. A graphql microservice running on port 3002
  3. A create-react-app app running on port 3000

The client (create-react-app) authenticates towards the ooth microservice, geting a JWT in exchange. This is then provided to the graphql microservice to start a user session. All further requests are made towards the graphql microservice.

The source code can be found here on github among all other ooth resources.

Ooth microservice

Let’s start with the authentication server. We create a new Ooth server object.

const Ooth = require('ooth')
const ooth = new Ooth({
mongoUrl: 'mongodb://localhost:27017/ooth',
sharedSecret: 'somesharedsecret'
})

This can then be extended with a strategy. Here we use a simple guest strategy, which allows one to create a user with no credentials at all.

const oothGuest = require('ooth-guest')
ooth.use('guest', oothGuest())

In the actual code you can find the configuration of an email/password strategy.

We create an express app and configure it with ooth:

const app = express()
ooth.start(app)
app.listen(3001)

GraphQL API

This is an extended version of my original GraphQL + MongoDB example. As the original, the schema features posts and comments. Additionally, it features a User type, a me query and authorIds attached to posts and comments.

Just like ooth, it’s an express app.

We configure CORS (this took me a while to get right, CORS with authentication cookies is quite tricky):

const corsMiddleware = cors({
origin: settings.originUrl,
credentials: true,
preflightContinue: false
})
app.use(corsMiddleware)
app.options(corsMiddleware)

We add session support to it:

app.use(cookieParser())
app.use(session({
name: 'api-session-id',
secret: 'somesessionsecret',
resave: false,
saveUninitialized: true
}))

and we also add passport.js

app.use(passport.initialize())

Now you might ask: “wasn’t the purpose of ooth to avoid having to use passport.js?”. Here’s the trick: ooth takes care of a whole variety of auth strategies, and of storing the user identity data (password hashes, tokens and so on) and keeping them consistent. No matter which strategy you use to authenticate, it returns a JWT, which can be used to authenticate and start a session with the actual app server. This means that all the app server has to know is how to read a JWT and how to start a session.

Here’s the session handling

app.use(passport.session())
passport.serializeUser((userId, done) => {
done(null, userId)
})
passport.deserializeUser((userId, done) => {
done(null, userId)
})

and here’s the JWT strategy:

passport.use('jwt', new JwtStrategy({
secretOrKey: 'somesharedsecret',
jwtFromRequest: ExtractJwt.fromAuthHeader() }, nodeifyAsync(async (payload) => {
if (!payload.user || !payload.user._id || typeof payload.user._id !== 'string') {
console.error(payload)
throw new Error('Malformed token payload.')
}
return payload.user._id
})))

All that remains is to add a login and logout endpoint:

app.post('/login', passport.authenticate('jwt'), (req, res) => {
res.send({
user: {
_id: req.user
},
message: 'Logged in successfully.'
})
})
app.post('/logout', (req, res) => {
req.logout()
res.send({
message: 'Logged out successfully.'
})
})

Create-React-App

It’s finally the app’s turn. We configure the ooth client:

import OothClient from 'ooth-client'

const ooth = new OothClient({
oothUrl: 'http://localhost:3001',
apiLoginUrl: 'http://localhost:3002/login',
apiLogoutUrl: 'http://localhost:3002/logout'
})

and we configure the apollo client:

import ApolloClient, {createNetworkInterface} from 'apollo-client'

const client = new ApolloClient({
networkInterface: createNetworkInterface({
uri: 'http://localhost:3002/graphql',
opts: {
credentials: 'include'
}
})
})

We configure the root component

class App extends Component {
render() {
return (
<OothProvider client={ooth}>
<ApolloProvider client={client}>
<UserProvider>
...
</UserProvider>
</ApolloProvider>
</OothProvider>
);
}
}

We can now write a simple component that handles login and logout of the user:

class LoginStatusComponent extends Component {
render() {
const {oothClient, user, refetchUser} = this.props
if (user) {
return <div>
Your user id is ${user._id}
<button onClick={() => {
oothClient.logout()
.then(res => {
refetchUser()
})
}}>Log out</button>
</div>
} else {
return <div>
Click on the button to create a guest session.<br/>
<button onClick={() => {
oothClient.authenticate('guest', 'register')
.then(res => {
console.log(res)
refetchUser()
})
.catch(err => {
console.log(err)
})
}}>Log in</button>
</div>
}
}
}
const LoginStatus = compose(
withOoth,
withUser
)(LoginStatusComponent)

That’s it!

  • withOoth injects the oothClient prop, which features the authenticate method, that first authenticates towards the ooth server, gets a JWT and provides it to the graphql server, thus starting a session.
  • withUser injects the user and refetchUser props. user is the result of a GraphQL me query.

For more details refer to the actal code.

Try it out online

I’ve implemented a very crude list of posts + their comments, and the ability to create a post or a comment, as soon as the user is logged in.

For a short time I’ll host the example here, so you can play with it:

  • http://nmr.io:5656 — ooth service
  • http://nmr.io:5657 — graphql service, see http://nmr.io:5657/graphiql
  • http://nmr.io:5658 — create-react-app

Future work: zeit.js integration

Create-react-app doesn’t feature server-side-rendering, which made a proof-of-concept easier to code. Next.js is a universal front-end, i.e. it renders both on the client as on the server. This makes authentication trickier.

There’s a long issue about implementing an authentication example, and practically all proposed solutions feature an external service. An integration with ooth would be the first example which people could host themselves. This will be my next challenge.

To find more info about future work check out the github roadmap.

Stay tuned to discover the ideal stack

All this innovation in the web-engineering world is ultimately about efficiently turning ideas into reality. Picking technologies, gluing them together, thinking about tradeoffs, yes, they are part of the daily life of a web engineer. But, if you think about it, this is all overhead. What we ultimately want, is to simply describe our ideas, and have them turn into a reality. It’s a long way, but in the end, this is what we are creating together: a technology stack that allows you to do just that. f(idea)=reality

This is what is so amazing about meteor’s vision. Create a project, describe what data you want and how it should look like, and it’s there for you, updated in real-time. Accounts? Server? Client? Mobile? Why have to think about those things? It’s all there for you.

Now, as I said elsewhere, meteor’s monolithic and deeply coupled design makes it too inflexible/slow to adapt to the coming innovations. We have to find a way to implement this vision within the larger, more distributed and agile (though sometimes fatiguing) node ecosystem. Among GraphQL, next.js, and possibly ooth, many pieces of the puzzle are sure to come. Follow me in this exciting journey.

Update: I created a node.js (next.js) starter library that provides the GUI side of Ooth, called Staart. Check it out live here!