HIGH graphql introspectionadonisjsjwt tokens

Graphql Introspection in Adonisjs with Jwt Tokens

Graphql Introspection in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

GraphQL introspection is a feature that allows clients to query the schema for types, queries, and mutations. In AdonisJS, when GraphQL is exposed via an HTTP endpoint and JWT tokens are used for authorization but not strictly required for introspection queries, the combination can unintentionally expose the API surface to unauthenticated discovery. Even when JWT tokens protect certain operations, misconfiguration can allow introspection without a token or with a token that lacks sufficient scope validation.

AdonisJS typically integrates GraphQL through packages such as adonisjs-addon-graphql or similar community packages. If the GraphQL route is mounted under a shared path (e.g., /graphql) and the server does not explicitly disable introspection for unauthenticated requests, an attacker can send an introspection query without presenting a valid JWT token. This reveals the full schema, including queries, mutations, and types, which can aid in reconnaissance for further attacks like BOLA or IDOR.

When JWT tokens are used, the risk arises if the authorization middleware does not block introspection for requests lacking a valid token or if introspection is allowed for tokens that do not have explicit permissions for schema discovery. For example, an API may validate JWT presence for data-fetching operations but fail to enforce the same check for the introspection query, effectively creating a bypass. Additionally, if the introspection endpoint is cached or logged, sensitive schema details may be exposed beyond the intended audience.

In practice, this means that an attacker can issue a POST request with an introspection query to the GraphQL endpoint while supplying an invalid, expired, or minimally scoped JWT token. If the server does not uniformly reject introspection based on authentication and authorization checks, the response may return the full schema, including sensitive field names and relationships, which can be leveraged to design more targeted attacks.

To determine whether this combination is vulnerable, you can use a scanner such as middleBrick, which performs unauthenticated and authenticated GraphQL introspection probes while validating JWT handling. The tool checks whether introspection is permitted without a token and whether tokens with limited scopes can still trigger schema exposure, producing findings mapped to the OWASP API Top 10 and providing remediation guidance.

Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes

To secure GraphQL introspection in AdonisJS when using JWT tokens, you must enforce authentication and authorization checks before allowing schema queries. The following examples demonstrate how to configure middleware and route handling to prevent unauthorized introspection.

First, ensure that your GraphQL endpoint is protected by JWT validation. In AdonisJS, you can use the auth middleware from the AdonisJS ACL or a custom JWT verification middleware. Here is an example of a custom JWT middleware that validates tokens before allowing access:

// start/middleware/verify-jwt.ts
import { Exception } from '@poppinss/utils'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { base64urlDecode } from 'jose'

export default class VerifyJwtMiddleware {
  public async handle({ request, response, next }: HttpContextContract) {
    const authHeader = request.headers().authorization
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      throw new Exception('Unauthorized: Missing token', 401, 'E_UNAUTHORIZED')
    }

    const token = authHeader.split(' ')[1]
    try {
      // Replace with your JWT verification logic, e.g., using jose or jsonwebtoken
      const { payload } = await jwtVerify(token, new TextEncoder().encode(process.env.JWT_SECRET || 'fallback-secret'))
      request.authUser = payload
      await next()
    } catch (error) {
      throw new Exception('Unauthorized: Invalid token', 401, 'E_INVALID_TOKEN')
    }
  }
}

Register this middleware in start/kernel.ts and apply it to your GraphQL route. For example, in start/routes.ts, you can define a protected route group:

// start/routes.ts
import Route from '@ioc:Adonis/Core/Route'
import VerifyJwt from 'App/Middleware/verify-jwt'

Route.group(() => {
  Route.post('/graphql', 'GraphqlController.invoke').middleware(['verify-jwt'])
}).prefix('api')

Next, in your GraphQL controller, explicitly disable introspection for requests that lack valid authentication or do not meet scope requirements. You can conditionally allow introspection only for admin-level tokens by inspecting the JWT payload:

// app/Controllers/Http/GraphqlController.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { GraphQLServer } from 'graphql-yoga'

export default class GraphqlController {
  public async invoke({ request, response }: HttpContextContract) {
    const isAuthenticated = !!request.authUser
    const userRole = request.authUser?.role || 'user'

    const server = new GraphQLServer({
      typeDefs: 'schema.graphql',
      resolvers: { /* resolvers */ },
    })

    // Disable introspection unless the user has admin scope
    if (request.url().includes('introspection') && !isAuthenticated) {
      return response.forbidden({ error: 'Introspection not allowed without authentication' })
    }

    if (userRole !== 'admin' && request.originalUrl().includes('__schema')) {
      return response.forbidden({ error: 'Insufficient permissions for schema introspection' })
    }

    const result = await server.executeHTTPRequest(request, response)
    return response.send(result)
  }
}

Additionally, you can configure the GraphQL server to reject introspection entirely in production by setting introspection to false in the server options when not in a development environment:

// app/Services/graphql.ts
import { GraphQLServer } from 'graphql-yoga'

const isProduction = process.env.NODE_ENV === 'production'

const server = new GraphQLServer({
  typeDefs: 'schema.graphql',
  resolvers: {},
  options: {
    introspection: !isProduction,
  },
})

export default server

These steps ensure that JWT tokens are required for introspection and that the token's scope or role is validated before exposing the schema. Combining middleware enforcement with server-level configuration reduces the risk of unintended schema disclosure.

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

Can introspection be allowed for certain roles while still using JWT tokens in AdonisJS?
Yes, you can conditionally allow introspection based on JWT payload claims such as role or scope. In your route handler or controller, inspect the authenticated user's role and permit introspection only for authorized roles, while returning a 403 for others.
Does middleBrick test for GraphQL introspection bypasses using JWT tokens?
Yes, middleBrick checks whether GraphQL introspection is permitted without a token and whether tokens with limited scopes can still trigger schema exposure, producing findings mapped to the OWASP API Top 10 and providing remediation guidance.