import { Callout, Tabs } from '@theguild/components'

# Authorization and Authentication

Hive Gateway supports Authentication and Authorization using JSON Web Tokens (JWT).

A [JSON Web Tokens (JWT)](https://jwt.io/) is a signed token containing arbitrary informations,
commonly used for authentication. By being signed by the issuer of the token, it can be verified
that the token is valid and has not been tampered with.

Hive Gateway provides a plugin to easily integrate JWT into your API, allowing you to easily
validate, decode and use the token (for identity and authorization).

Once you have the JWT token extract and validated, the JWT claims (and optionally, the full token)
are injected to the Hive Gateway execution context, and forwarded to upstream GraphQL subgraphs,
using the `extensions` field.

<Callout type="warning">

When JWT is enabled and claims are forwarded to the upstream GraphQL subgraphs, you might want to
use [HMAC Signature](/docs/gateway/other-features/security/hmac-signature) between your Hive Gateway
and the subgraphs. This will ensure that the requests to the subgraphs are trusted and signed by the
gateway, and no other entity can execute requests to the subgraph on behalf of the end-users.

</Callout>

<Callout>
  You can refer to [Generic Auth plugin docs](https://www.npmjs.com/package/@envelop/generic-auth),
  if you need a more customized auth setup without JWT.
</Callout>

## How to use?

Here's a mininal example for configuring the JWT plugin with a local signing key, and looking for
the token in the `authorization` header:

```ts filename="gateway.config.ts"
import {
  createInlineSigningKeyProvider,
  defineConfig,
  extractFromHeader
} from '@graphql-hive/gateway'

const signingKey = 'my-secret-key'

export const gatewayConfig = defineConfig({
  jwt: {
    // Look and extract for the token in the 'authorization' header, with the 'Bearer' prefix.
    tokenLookupLocations: [extractFromHeader({ name: 'authorization', prefix: 'Bearer' })],
    // Decode and validate the token using the provided signing key.
    singingKeyProviders: [createInlineSigningKeyProvider(signingKey)],
    // Forward the verified token payload to the upstream GraphQL subgraphs.
    forward: {
      payload: true
    }
  }
})
```

You can also pass
[additional configuration options to the Yoga plugin](https://the-guild.dev/graphql/yoga-server/docs/features/jwt#additional-configuration):

```ts filename="gateway.config.ts"
import { defineConfig, createInlineSigningKeyProvider, createRemoteJwksSigningKeyProvider, extractFromHeader, extractFromCookie } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  jwt: {
      // Forward the extracted token and claims to the upstream GraphQL subgraphs.
      forward: {
        payload: true, // optional, default is "true"
        token: false, // optional, default is "false"
        extensionsFieldName: "jwt", // optional, default is "jwt"
      },
      // Configure your signing providers: either a local signing-key or a remote JWKS are supported.
      singingKeyProviders: [
        createInlineSigningKeyProvider(signingKey),
        createRemoteJwksSigningKeyProvider({ jwksUri: 'https://example.com/.well-known/jwks.json' })
      ]
      // Configure where to look for the JWT token: in the headers, or cookies.
      // By default, the plugin will look for the token in the 'authorization' header only.
      tokentokenLookupLocations: [
        extractFromHeader({ name: 'authorization', prefix: 'Bearer' }),
        extractFromCookie({ name: 'auth' }),
      ],
      // Configure your token issuers/audience/algorithms verification options.
      // By default, the plugin will only verify the HS256/RS256 algorithms.
      // Please note that this should match the JWT signer issuer/audience/algorithms.
      tokenVerification: {
        issuer: 'http://my-issuer.com',
        audience: 'my-audience',
        algorithms: ['HS256', 'RS256'],
      },
      // The plugin can reject the request if the token is missing or invalid (doesn't pass JWT `verify` flow).
      // By default, the plugin will reject the request if the token is missing or invalid.
      reject: {
        missingToken: true,
        invalidToken: true,
      }
  }
})
```

## Configuration Options

Please refer to the
[configuration options of the Yoga plugin](https://the-guild.dev/graphql/yoga-server/docs/features/jwt#additional-configuration)
for complete details and examples.

## Forwarding the JWT token and payload

The JWT token and payload can be forwarded to the upstream GraphQL subgraphs, using the `extensions`
field of the request body.

This workflow can allow you to easily delegate the authentication process to Hive Gateway, and allow
the subgraphs to deal only with the user identity and authorization.

```mermaid
flowchart LR
	1(["End-user"]) --->|"query { comments { id author { id name }}}"| 2

    subgraph Hive Gateway
    2["Engine"]
    3["JWT Plugin"]
    4["Query Planner"]
    2--->|"Bearer XYZ"|3
    3--->|"{ sub: 123 }"|2
    2--->4
    end

    subgraph "Users"
        5["Users Subgraph"]
        4--->|"query { _entities(representations: $r) { ... on User { name }} }\nextensions: { jwt: { payload: { sub: 123 }}}"|5
    end

    subgraph "Comments"
        6["Comments Subgraph"]

        4--->|"query { comments { id author { id }} }\nextensions: { jwt: { payload: { sub: 123 }}}"|6
    end
```

To pass the full token payload, you can use the `forwarded.claims` option:

```ts
{
  forward: {
    payload: true // optional, default is "true"
  }
}
```

The token payload will be injected into `extensions.jwt.payload` of the upstream request body:

```json
{
  "query": "{ comments { id author { id }} }",
  "extensions": {
    "jwt": {
      "payload": {
        "sub": 123
      }
    }
  }
}
```

You can also pass the full token, using the `forwarded.token` option:

```ts
{
  forward: {
    payload: true, // optional, default is "true"
    token: true // optional, default is "false"
  }
}
```

And the token and (optional) prefix will be injected into `extensions.jwt.token` of the upstream
HTTP request:

```json
{
  "query": "{ comments { id author { id }} }",
  "extensions": {
    "jwt": {
      "payload": {
        "sub": 123
      },
      "token": {
        "value": "XYZ",
        "prefix": "Bearer"
      }
    }
  }
}
```

Additionally, if you wish to change the name of the `jwt` field in the extensions, you can use the
`forwarded.extensionsFieldName` option to change it:

```ts
{
  forward: {
    extensionsFieldName: 'myJwt' // optional, default is "jwt"
  }
}
```

## Using the JWT token

### Within Gateway

The JWT plugin will inject the decoded token and payload into the context of Hive Gateway.

You can use the injected payload with other plugins, to implement things like authorization or
user-identity based logic.

For example, with a plugin like Operation Field Permissions, you can use the `jwt` property of the
context to access the decoded JWT token, and decide what permissions to allow to the user based on
identity or token claims:

```ts filename="gateway.config.ts"
import { useOperationFieldPermissions } from '@envelop/operation-field-permissions'
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  // ...
  jwt: {
    // ...
  },
  plugins: () => [
    useOperationFieldPermissions({
      getPermissions: async context => {
        const { jwt } = context

        // Check based on identity / user-id.
        if (jwt?.payload?.sub === '123') {
          return new Set(['Query.*'])
        }

        // Check based on token payload
        if (jwt?.payload?.role === 'admin') {
          return new Set(['Query.*'])
        }

        // Default permissions
        return new Set(['Query.greetings'])
      }
    })
  ]
})
```

### In upstream GraphQL subgraphs

<Tabs items={['GraphQL-Yoga', 'Apollo Server', 'Other GraphQL servers']}>

{/* GraphQL-Yoga */}

<Tabs.Tab>

The JWT token and claims are forwarded to the upstream GraphQL subgraphs, using the `extensions`
field.

To access the JWT token and claims in your upstream service resolvers/execution, you can use the
`extensions` field of the incoming GraphQL request.

If you are using [GraphQL-Yoga](https://the-guild.dev/graphql/yoga-server) for your upstream
subgraph implementation, you can use a built-in utility for extracting it for you in an easy way:

```ts filename="yoga-subgraph.ts"
import { useForwardedJWT } from '@graphql-hive/gateway'

const myYogaSubgraphServer = createYoga({
  schema: mySchema,
  plugins: [
    useForwardedJWT({
      // The name of the field in the extensions object, default is "jwt"
      extensionsFieldName: 'jwt',
      // The name of the field to inject into the local context object, default is "jwt"
      extendContextFieldName: 'jwt'
    })
  ]
})
```

With this plugin configured, you should be able to just access `context.jwt` in your subgraphs, just
like you would in the gateway.

This makes the process of integrating JWT easier, and streamlined across the whole flow of
execution.

</Tabs.Tab>

{/* Apollo Server */}

<Tabs.Tab>

With Apollo-Server, you can access the forwarded claims/token, using a custom Apollo-Server plugin
that extracts `extensions` and injects it into the context:

```ts filename="apollo-subgraph.ts"
import { ApolloServer, ApolloServerPlugin } from '@apollo/server'

const extractJwtPlugin = {
  async requestDidStart({ request, contextValue }) {
    contextValue.jwt = request.extensions?.jwt
  }
} satisfies ApolloServerPlugin<{ jwt?: { payload: Record<string, any> } }>

const server = new ApolloServer({
  // Now, in your schema resolvers, you can access the JWT token and claims using `context.jwt`.
  plugins: [extractJwtPlugin]
  // ...
})
```

</Tabs.Tab>

{/* Other GraphQL servers */}

<Tabs.Tab>

Other implementations for GraphQL subgraph servers can also access the JWT token and claims, by
looking at the `extensions` field of the incoming request.

The `extensions` field of the incoming request will contain the JWT token and claims, injected by
Hive Gateway, following this structure:

```json
{
  "extensions": {
    "jwt": {
      "payload": {
        "sub": 123
      },
      // optional, depends on the gateway plugin configuration
      "token": {
        "value": "XYZ",
        "prefix": "Bearer"
      }
    }
  }
}
```

</Tabs.Tab>

</Tabs>

## Additional Configuration

### Token lookup

The plugin can be configured to look for the JWT token in different locations:

<Tabs items={['HTTP Header', 'HTTP Cookie', 'Custom Function']}>

{/* HTTP Header */}

<Tabs.Tab>

By default, the plugin will look for the token in the `authorization` header. You can configure the
plugin to look for the token in a different header or with a different prefix.

The prefix is being validated along with the token (for example: `Bearer my-token`).

```ts
import { defineConfig, extractFromHeader } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  // ...
  jwt: {
    // ...
    tokenLookupLocations: [extractFromHeader({ name: 'x-auth-token', prefix: 'Bearer' })]
  }
})
```

</Tabs.Tab>

{/* HTTP Cookie */}

<Tabs.Tab>

You can configure the plugin to look for the token in a cookie. To do so, you need to enable cookie
parsing in the gateway.

```ts
import { extractFromCookie, defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  // ...
  jwt: {
    // ...
    tokenLookupLocations: [extractFromCookie({ name: 'my-cookie' })]
  }
  // Make sure you enabled cookie parsing in the gateway
  cookies: true,
})
```

</Tabs.Tab>

{/* Custom Function */}

<Tabs.Tab>

You can configure the plugin to use a custom function to look for the token:

```ts
import { defineConfig } from '@graphql-hive/gateway'

const getToken = ({ request, serverContext, url }) => {
  return request.headers.get('x-my-token')
}

export const gatewayConfig = defineConfig({
  // ...
  jwt: {
    // ...
    tokenLookupLocations: [getToken]
  }
})
```

</Tabs.Tab>

{/* Multiple locations */}

<Tabs.Tab>

You can configure the plugin to look for the token in multiple locations. The plugin will look for
the token in the order you provide.

```ts
import { defineConfig, extractFromCookie, extractFromHeader } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  // ...
  jwt: {
    // ...
    tokenLookupLocations: [
      extractFromHeader({ name: 'authorization', prefix: 'Bearer' }),
      extractFromHeader({ name: 'x-legacy-auth' }),
      extractFromHeader({ name: 'x-api-key', prefix: 'API-Access' }),
      extractFromCookie({ name: 'browserAuth' })
    ]
  }
})
```

</Tabs.Tab>

</Tabs>

---

### Signing Key providers

The plugin can be configured to use different signing key providers:

<Tabs items={['Inline', 'Remote JWKS', 'Multiple providers']}>

{/* Inline */}

<Tabs.Tab>

You can provide the signing key directly in the configuration.

> Do not hardcode the signing key in your code. Use environment variables, local encrypted file or a
> secret store!

```ts
import { createInlineSigningKeyProvider, defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  // ...
  jwt: {
    // ...
    singingKeyProviders: [createInlineSigningKeyProvider(process.env.MY_JWT_SECRET)]
  }
})
```

> In case you are using an inline signing key provider, all `keyid` / `kid` will be allowed in
> tokens.

</Tabs.Tab>

{/* Remote JWKS */}

<Tabs.Tab>

You can configure the plugin to fetch the signing key from a remote JWKS endpoint.

Provide a `jwksClientOptions` object (see
[node-jwks-rsa documentation](https://github.com/auth0/node-jwks-rsa/blob/master/EXAMPLES.md)].

```ts
import { createRemoteJwksSigningKeyProvider, defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  // ...
  jwt: {
    // ...
    singingKeyProviders: [
      createRemoteJwksSigningKeyProvider({
        jwksUri: 'https://example.com/.well-known/jwks.json'
      })
    ]
  }
})
```

</Tabs.Tab>

{/* Multiple providers */}

<Tabs.Tab>

When using multiple providers, the plugin will try to use the first available signing key.

```ts
import {
  createInlineSigningKeyProvider,
  createRemoteJwksSigningKeyProvider,
  defineConfig
} from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  // ...
  jwt: {
    // ...
    singingKeyProviders: [
      // In case your remote provider is not available, the plugin will try use the inline provider.
      createRemoteJwksSigningKeyProvider({
        jwksUri: 'https://example.com/.well-known/jwks.json'
      }),
      createInlineSigningKeyProvider(process.env.MY_JWT_SECRET)
    ]
  }
})
```

</Tabs.Tab>

</Tabs>

---

### Token Verification

The plugin verification process can be customized to match the JWT token `issuer`, `audience`, and
algorithms.

> Note that the verification options should match the JWT signer's configuration.

You can find
[here the complete list of verification options](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jsonwebtoken/index.d.ts#L58-L77)
for this plugin.

```ts
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  // ...
  jwt: {
    // ...
    tokenVerification: {
      issuer: ['http://yoga'],
      audience: 'my-audience',
      algorithms: ['HS256', 'RS256']
    }
  }
})
```

### Execution Rejection

The plugin can be configured to reject the request if the token is missing or invalid.

By default, an authentication error will be thrown if the token is missing or invalid, and the
request will be reject with status code `401`.

```ts
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  // ...
  jwt: {
    // ...
    reject: {
      missingToken: true,
      invalidToken: true
    }
  }
})
```

> In case you want to handle the error yourself, you can set
> `reject: { missingToken: false, invalidToken: false }` and handle the error in your resolvers. The
> `context.jwt` will be `undefined` in case of missing or invalid token.

## Granular Protection using Auth Directives (`@authenticated`, `@requiresScopes` and `@policy`)

### Configuration

By default, the JWT plugin protects the whole schema. If you want to use a granular protection by
using Federation directives such as `@authenticated`, `@requiresScopes` and `@policy`, you can use
the Generic Auth plugin to have a granular protection using with or without JWT.

With the following configuration, you can use the JWT plugin to extract the token and claims, and
then use the Generic Auth plugin to protect the schema with the Federation directives:

```ts
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
  // ...
  jwt: {
    // You have to disable the default rejection of the JWT plugin
    reject: {
      missingToken: false,
      invalidToken: false
    }
  },
  genericAuth: {
    // Then set generic auth plugin to use granular mode
    mode: 'protect-granular',
    // Set where to extract the payload
    resolveUserFn: ctx => ctx.jwt?.payload,
    // If you want to continue execution even if some fields are rejected
    rejectUnauthenticated: false
  }
})
```

### Protect a field using a field `@authenticated`

In your GraphQL schema SDL, you can add `@authenticated` directive to your fields.

```graphql
# Import it from Federation spec
extend schema @link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@authenticated"])

type Query {
  me: User! @authenticated
  protectedField: String @authenticated
  # publicField: String
}
```

> You can apply that directive to any GraphQL `field` definition, not only to root fields.

### Role/scope based authentication (RBAC) with `@requiresScope` directive

You can use `@requiresScope` directive to protect your schema based on the user's role or scope.
Here's an example of how you can use it:

```graphql
extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@requiresScopes"])

type Query {
  me: User! @requiresScopes(scopes: [["read:user"]])
  protectedField: String @requiresScopes(scopes: [["read:admin"]])
  publicField: String
}
```

By default, the plugin will try to extract available scopes for the current payload from `scope`
property which is expected to be a string like `read:user read:admin`. However you can customize
this behavior by providing a custom `extractScopes` function.

```ts
{
  validateUser,
  mode: 'protect-granular',
  // Set where to extract the payload
  resolveUserFn: ctx => ctx.jwt?.payload,
  extractScopes: jwtPayload => jwtPayload?.scopes // Expected to return an array of strings
}
```

You can also apply `AND` or `OR` logic to the scopes:

```graphql
extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@requiresScopes"])

type Query {
  # This field requires the user to have `read:user` OR `read:admin` scopes
  me: User! @requiresScopes(scopes: [["read:user"], ["read:admin"]])
  # This field requires the user to have `read:user` AND `read:admin` scopes
  protectedField: String @requiresScopes(scopes: [["read:admin", "read:user"]])
  publicField: String
}
```

### `@policy` directive to fetch the roles from a policy service

You can use the `@policy` directive to fetch the roles from a policy service. Here's an example of
how you can use it:

```graphql
extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@policy"])

type Query {
  me: User! @policy(policies: [["read:user"]])
  protectedField: String @policy(policies: [["read:admin"]])
  publicField: String
}
```

It has the same logic with `@requiresScopes` but it can asynchronously fetch the roles from a
source;

```ts
{
  resolveUserFn,
  validateUser,
  mode: 'protect-granular',
  fetchPolicies: async user => {
    const res = await fetch('https://policy-service.com', {
      headers: {
        Authorization: `Bearer ${user.token}`
      }
    })
    // Expected to return an array of strings
    return res.json()
  }
}
```
