meta pixel

JWT authentication using @apollo/gateway

With this article you're going to learn how to set up an application using Apollo-Gateway and Apollo-Federation

    Posted byLeonardo Habitz|on Apr 29th 2025|3 min read

    Technology Expert

Apollo

Apollo is a platform that exposes a bunch of tools to work with graphql such as apollo-server and apollo-gateway. I’m going to show you how to design your microservices architecture with public and private mutations using the gateway engine provided by the apollo community.


Apollo gateway

Apollo gateway is an amazing tool that helps us to have a single entry point for our microservices.

And why do you need it? When you split your services into different API’s you have several addresses available in your cluster, each address corresponding to one microservice. For instance: http://localhost:3000, http://localhost:4000, etc.

The problem is that all the clients will have to store all of these urls to make the requests to the right services, that’s awful! This approach has so many problems, you can get more info about that here.

So, there’s the apollo-gateway! It’s the apollo solution for graphql microservices running in the apollo-server tool.


Apollo federation

Another tool that I used in this project is the apollo federation, it provides a helpful ability to put all the microservices together into a unique graph.

A unique graph, or single data graph, it’s a nice to have once you have (and you will) queries and mutations which needs more than one service to been called. An example is querying by a specific user and his products, assuming that you had split the user and products queries in two different services, you need a single graph to mix the information into one source.


Show us the code!

So, Let’s get started by creating the auth service, this API will have only one mutation called login and will be responsible for generating the jwt token.

package.json:

{

"name": "auth",

"scripts": {

"start": "node index.js"

},

"dependencies": {

"@apollo/federation": "^0.9.4",

"apollo-server": "^2.9.3",

"graphql": "^14.4.2",

"jsonwebtoken": "^8.5.1"

}

}

index.js:

const { ApolloServer, gql } = require('apollo-server')

const { buildFederatedSchema } = require('@apollo/federation')

const login = require('./login')

const mock = () => { }

const typeDefs = gql`

extend type Query {

mock: String

}

type LoginResponse {

token: String

}

input LoginInput {

email: String!

password: String!

}

type Mutation {

login(input: LoginInput): LoginResponse!

}

`

const resolvers = {

Query: { mock },

Mutation: { login }

}

const server = new ApolloServer({

schema: buildFederatedSchema([{ resolvers, typeDefs }])

})

server.listen({ port: 3000, cors: { origin: '*' } })

Note that the typeDefs has a mocked query, that’s because an error is displayed when a service has no query.


login.js

const { sign } = require('jsonwebtoken')

const login = (_, { input }) => {

if (input.email != 'admin' || input.password != '123') {

throw new Error('Invalid credentials')

}

return {

token: sign({ userId: 1 }, 'privateKey', { expiresIn: '1h' })

}

}

module.exports = login

Here the token is created using a securityKey (privateKey), containing the user and expires info. The token will be something like this:

‘eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTU3MjI2MzI4OSwiZXhwIjoxNTcyMjY2ODg5fQ.DabTdm3tImWD3ekboYyrXxPL4ZefL4og2Xpg9mR8FJo’


The products API.

package.json:


{

"name": "products",

"scripts": {

"start": "node index.js"

},

"dependencies": {

"@apollo/federation": "^0.9.4",

"apollo-server": "^2.9.3",

"graphql": "^14.4.2"

}

}

index.js

const { ApolloServer, gql } = require('apollo-server')

const { buildFederatedSchema } = require('@apollo/federation')

const products = (_, __, { userId }) => [{

id: 1,

userId,

name: 'Shoes'

}, {

id: 2,

userId,

name: 'Jacket'

}]

const typeDefs = gql`

type Product {

id: ID

userId: ID

name: String

}

extend type Query {

products: [Product]

}

`

const resolvers = {

Query: { products }

}

const server = new ApolloServer({

schema: buildFederatedSchema([{ resolvers, typeDefs }]),

context: ({ req }) => ({ userId: req.headers['user-id'] })

})

server.listen({ port: 4000, cors: { origin: '*' } })

In the example above we’re receiving the userId through the request headers and putting it into the context to show you how to access it from the resolvers.


And then the gateway

package.json


{

"name": "gateway",

"scripts": {

"start": "node index.js"

},

"dependencies": {

"@apollo/gateway": "^0.10.4",

"apollo-server": "^2.9.3",

"graphql": "^14.4.2",

"jsonwebtoken": "^8.5.1"

}

}

handleAuth.js

const { verify } = require('jsonwebtoken')


const PUBLIC_ACTIONS = ["login"]

const actionIsPublic = ({ query }) => (

PUBLIC_ACTIONS.some(action => query.includes(action))

)

const isIntrospectionQuery = ({ operationName }) => (

operationName === 'IntrospectionQuery'

)

const shouldAuthenticate = body => (

!isIntrospectionQuery(body) && !actionIsPublic(body)

)

const handleAuth = ({ req }) => {

if (shouldAuthenticate(req.body)) {

const decoded = verify(req.headers.authorization, 'privateKey')

return { userId: decoded.userId }

}

}

module.exports = handleAuth

Here we’re skipping the public mutations and queries from the auth validation (in this case just the login one) and also preventing the Introspection query to be validated. Then we verify the token received by the authorization header (using the same securityKey as shown before at the auth service) and put the userId extracted from the token into the gateway context. If the token is expired or invalid an error will be thrown

index.js

const { ApolloServer } = require('apollo-server')

const { ApolloGateway, RemoteGraphQLDataSource } = require('@apollo/gateway')

const handleAuth = require('./handleAuth')

const server = new ApolloServer({

gateway: new ApolloGateway({

serviceList: [

{ name: 'auth', url: 'http://localhost:3000' },

{ name: 'products', url: 'http://localhost:4000' }

],

buildService({ name, url }) {

return new RemoteGraphQLDataSource({

url,

willSendRequest({ request, context }) {

request.http.headers.set('user-id', context.userId)

}

})

}

}),

context: handleAuth,

subscriptions: false

})

const serverConfig = { port: 8080, cors: { origin: '*' } }

server.listen(serverConfig).then(({ url }) => {

console.log(`🚀 Server ready at ${url}`)

})

Now we just call the handleAuth function and then get the userId from the context to pass along to services by headers, that’s how the products service has access to the userId decoded via the request headers. This process happens in every request.


Further reading

If you are interested in studying how to configure your own gateway check the docs here. And here are the apollo-federation docs. Also, go to my github’s repo to see the full example.

About the Author

Leonardo Habitz
Leonardo Habitz

I'm a Senior Software Engineer / Technical Leader focused on React, Typescript, and NodeJS with 8+ years of experience. I also work as a mentor and instructor.

Expertise

Google Analytics 1+ yrs
Assembly language 4+ yrs
Continuous Integration 4+ yrs
AWS (Amazon Web Services) 2+ yrs
Requirements Gathering 2+ yrs
Leonardo Habitz

Leonardo Habitz

Technology Expert


Trending Posts

Global HR>Global Employment

Payroll Compliance Checklist and Tax Law Guide for 2025

Highlight the critical nature of staying informed on payroll compliance and tax laws for tech companies with a global footprint in 2025.

Brian Mc Sweeney
Brian Mc SweeneyTechnology • 11+ years
Project & Product>Methodology

Agile Project Management: Key Benefits for Modern Product Teams

In the ever-evolving landscape of product development, staying ahead of the curve is paramount.

Yana Bell
Yana BellHuman Resources • 10+ years
Future of work

The Future of Remote Work: Trends and Predictions for 2025 and Beyond

Remote work is no longer just a trend; it's becoming a standard practice in the global workforce.

Stephen Mc Sweeney
Stephen Mc SweeneyOperations • 11+ years
Leonardo Habitz

Leonardo Habitz

Technology Expert


Trending Posts

Global HR>Global Employment

Payroll Compliance Checklist and Tax Law Guide for 2025

Highlight the critical nature of staying informed on payroll compliance and tax laws for tech companies with a global footprint in 2025.

Brian Mc Sweeney
Brian Mc SweeneyTechnology • 11+ years
Project & Product>Methodology

Agile Project Management: Key Benefits for Modern Product Teams

In the ever-evolving landscape of product development, staying ahead of the curve is paramount.

Yana Bell
Yana BellHuman Resources • 10+ years
Future of work

The Future of Remote Work: Trends and Predictions for 2025 and Beyond

Remote work is no longer just a trend; it's becoming a standard practice in the global workforce.

Stephen Mc Sweeney
Stephen Mc SweeneyOperations • 11+ years