When starting a backend project, two of the biggest concerns will usually be the right structure of the project and authentication. If you could skip thinking and planning about these two, starting a new backend project can be much easier.

💡

If you haven’t checked out our blog post about authentication and authorization in GraphQL Modules, please read that before!

Internally, we use GraphQL-Modules and accounts-js to help us with those two decisions, GraphQL-Modules helps us solve our architectural problems in modular, schema-first approaches with the power of GraphQL and accounts-js helps us create our authentication solutions by providing a simple API together with client and server libraries that saves us a lot of the groundwork around authentication.

If you haven’t heard about accounts-js before, it is a set of libraries to provide a full-stack authentication and accounts-management solutions for Javascript.

It is really customizable; so you can write any plugins for your own authentication methods or use the already existing email-password or the Facebook and Twitter OAuth integration packages.

accounts-js has connector libraries for MongoDB and Redis, but you can write your own database handler by implementing a simple interface.

accounts-js provides a ready to use GraphQL API if you install their GraphQL library, and we are happy to announce that the GraphQL library is now internally built using GraphQL-Modules!

It doesn’t affect people who are not using GraphQL Modules, but it helps the maintainers of accounts-js and simplifies the integration for GraphQL-Modules-based projects.

How to Implement Server-Side Using accounts-js, GraphQL Modules and Apollo Server

First install required dependencies from npm or yarn:

yarn add mongodb @accounts/server @accounts/password @accounts/database-manager @accounts/mongo @accounts/graphql-api @graphql-modules/core apollo-server graphql-import-node

Let’s assume that we’re using MongoDB as our database, password-based authentication and ApolloServer:

import 'graphql-import-node' import { ApolloServer } from 'apollo-server' import { MongoClient } from 'mongodb' import { DatabaseManager } from '@accounts/database-manager' import { AccountsModule } from '@accounts/graphql-api' import MongoDBInterface from '@accounts/mongo' import { AccountsPassword } from '@accounts/password' import { AccountsServer } from '@accounts/server' import { resolvers } from './resolvers' import * as typeDefs from './typeDefs.graphql' const PORT = process.env.MONGO_URI || 4000 const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017/myDb' const TOKEN_SECRET = process.env.TOKEN_SECRET || 'myTokenSecret' async function main() { const mongoClient = await MongoClient.connect(MONGO_URI, { useNewUrlParser: true, native_parser: true }) const db = mongoClient.db() const userStorage = new MongoDBInterface(db, { convertUserIdToMongoObjectId: false }) // Create database manager (create user, find users, sessions etc) for accounts-js const accountsDb = new DatabaseManager({ sessionStorage: userStorage, userStorage }) // Create accounts server that holds a lower level of all accounts operations const accountsServer = new AccountsServer( { db: accountsDb, tokenSecret: TOKEN_SECRET }, { password: new AccountsPassword() } ) const { schema } = new GraphQLModule({ typeDefs, resolvers, imports: [AccountsModule.forRoot({ accountsServer })], providers: [ { provide: Db, useValue: db // Use MongoDB's instance inside DI } ] }) const apolloServer = new ApolloServer({ schema, context: session => session, introspection: true }) const { url } = await apolloServer.listen(PORT) console.log(`Server listening: ${url}`) } main()

And we can extend User type with custom fields in our schema, and add a mutation which is restricted to authenticated clients.

type Query { allPosts: [Post] } type Mutation { addPost(title: String, content: String): ID @auth } type User { posts: [Post] } type Post { id: ID title: String content: String author: User }

Finally, let’s define some resolvers for it:

export const resolvers = { User: { posts({ _id }, args, { injector }) { const db = injector.get(Db) const Posts = db.collection('posts') return Posts.find({ userId: _id }).toArray() } }, Post: { id: ({ _id }) => _id, author({ userId }, args, { injector }) { const accountsServer = injector.get(AccountsServer) return accountsServer.findUserById(userId) } }, Query: { allPosts(root, args, { injector }) { const db = injector.get(Db) const Posts = db.collection('posts') return Posts.find().toArray() } }, Mutation: { addPost(root, { title, content }, { injector, userId }: ModuleContext<AccountsContext>) { const db = injector.get(Db) const Posts = db.collection('posts') const { insertedId } = Posts.insertOne({ title, content, userId }) return insertedId } } }

When you print the whole app’s schema, you would see something like above:

type TwoFactorSecretKey { ascii: String base32: String hex: String qr_code_ascii: String qr_code_hex: String qr_code_base32: String google_auth_qr: String otpauth_url: String } input TwoFactorSecretKeyInput { ascii: String base32: String hex: String qr_code_ascii: String qr_code_hex: String qr_code_base32: String google_auth_qr: String otpauth_url: String } input CreateUserInput { username: String email: String password: String } type Query { twoFactorSecret: TwoFactorSecretKey getUser: User allPosts: [Post] } type Mutation { createUser(user: CreateUserInput!): ID verifyEmail(token: String!): Boolean resetPassword(token: String!, newPassword: String!): Boolean sendVerificationEmail(email: String!): Boolean sendResetPasswordEmail(email: String!): Boolean changePassword(oldPassword: String!, newPassword: String!): Boolean twoFactorSet(secret: TwoFactorSecretKeyInput!, code: String!): Boolean twoFactorUnset(code: String!): Boolean impersonate(accessToken: String!, username: String!): ImpersonateReturn refreshTokens(accessToken: String!, refreshToken: String!): LoginResult logout: Boolean authenticate(serviceName: String!, params: AuthenticateParamsInput!): LoginResult addPost(title: String, content: String): Post } type Tokens { refreshToken: String accessToken: String } type LoginResult { sessionId: String tokens: Tokens } type ImpersonateReturn { authorized: Boolean tokens: Tokens user: User } type EmailRecord { address: String verified: Boolean } type User { id: ID! emails: [EmailRecord!] username: String posts: [Post] } input UserInput { id: ID email: String username: String } input AuthenticateParamsInput { access_token: String access_token_secret: String provider: String password: String user: UserInput code: String } type Post { id: ID title: String content: String author: User }

How to Implement Client-Side Using accounts-js, React and Apollo-Client

Now we can create a simple frontend app by using Apollo-Client and accounts-js client for this backend app. The example below shows some example code that works on these two.

import React, { Component } from 'react' import { render } from 'react-dom' import ApolloClient from 'apollo-boost' import gql from 'graphql-tag' import { ApolloProvider, Mutation, Query } from 'react-apollo' import { AccountsClient } from '@accounts/client' import { AccountsClientPassword } from '@accounts/client-password' import GraphQLClient from '@accounts/graphql-client' const apolloClient = new ApolloClient({ async request(operation) { const tokens = await accountsClient.getTokens() if (tokens) { operation.setContext({ headers: { 'accounts-access-token': tokens.accessToken } }) } }, uri: 'http://localhost:4000/graphql' }) const accountsGraphQL = new GraphQLClient({ graphQLClient: apolloClient }) const accountsClient = new AccountsClient({}, accountsGraphQL) const accountsPassword = new AccountsClientPassword(accountsClient) const ALL_POSTS_QUERY = gql` query AllPosts { allPosts { id title content author { username } } } ` const ADD_POST_MUTATION = gql` mutation AddPost($title: String, $content: String) { addPost(title: $title, content: $content) } ` class App extends Component { state = { credentials: { username: '', password: '' }, newPost: { title: '', content: '' }, user: null } componentDidMount() { return this.updateUserState() } async updateUserState() { const tokens = await accountsClient.refreshSession() if (tokens) { const user = await accountsGraphQL.getUser() await this.setState({ user }) } } renderAllPosts() { return ( <Query query={ALL_POSTS_QUERY}> {({ data, loading, error }) => { if (loading) { return <p>Loading...</p> } if (error) { return <p>Error: {error}</p> } return data.allPosts.map((post: any) => ( <li> <p>{post.title}</p> <p>{post.content}</p> <p>Author: {post.author.username}</p> </li> )) }} </Query> ) } renderLoginRegister() { return ( <fieldset> <legend>Login - Register</legend> <form> <p> <label> Username: <input value={this.state.credentials.username} onChange={e => this.setState({ credentials: { ...this.state.credentials, username: e.target.value } }) } /> </label> </p> <p> <label> Password: <input value={this.state.credentials.password} onChange={e => this.setState({ credentials: { ...this.state.credentials, password: e.target.value } }) } /> </label> </p> <p> <button onClick={e => { e.preventDefault() accountsPassword .login({ password: this.state.credentials.password, user: { username: this.state.credentials.username } }) .then(() => this.updateUserState()) }} > Login </button> </p> <p> <button onClick={e => { e.preventDefault() accountsPassword .createUser({ password: this.state.credentials.password, username: this.state.credentials.username }) .then(() => { alert('Please login with your new credentials') this.setState({ credentials: { username: '', password: '' } }) }) }} > Register </button> </p> </form> </fieldset> ) } renderAddPost() { return ( <Mutation mutation={ADD_POST_MUTATION}> {addPost => ( <fieldset> <legend>Add Post</legend> <form> <p> <label> Title: <input value={this.state.newPost.title} onChange={e => { this.setState({ newPost: { ...this.state.newPost, title: e.target.value } }) }} /> </label> </p> <p> <label> Content: <input value={this.state.newPost.content} onChange={e => { this.setState({ newPost: { ...this.state.newPost, content: e.target.value } }) }} /> </label> </p> <p> <input type="submit" onClick={e => { e.preventDefault() addPost({ variables: { title: this.state.newPost.title, content: this.state.newPost.content } }) }} /> </p> </form> </fieldset> )} </Mutation> ) } render() { return ( <div> <h2>All Posts</h2> {this.renderAllPosts()} {this.state.user ? this.renderAddPost() : this.renderLoginRegister()} </div> ) } } render( <ApolloProvider client={apolloClient}> <App /> </ApolloProvider>, document.getElementById('root') )

As you can see from the example, it can be really easy to create an application that has authentication in modular and future-proof approach.

You can learn more about accounts-js from the docs of this great library for more features such as Two-Factor Authentication and Facebook and Twitter integration using OAuth.

Also, you can learn more about GraphQL-Modules on the website and see how you can add GraphQL Modules features into your system in a gradual and selective way.

If you want strict types based on GraphQL Schema, for each module, GraphQL Code Generator has built-in support for GraphQL-Modules based projects. See the docs for more details.

You can check out our example about this integration https://github.com/ardatan/graphql-modules-accountsjs-boilerplate.

All Posts about GraphQL Modules

Last updated on