Securing Schemas and Endpoints

As you build out your GraphQL APIs, you can add access control mechanisms to prevent unauthorized use of the endpoints.

API Connect for GraphQL supports two different solutions for prevent unauthorized access to schemas and endpoints:

  • Admin keys and API keys: You can use Admin keys and API keys to control access to the entire endpoint. API keys are the default access control mechanism.
  • Field Policies: You can use field policies to control access to specific entry point fields on the endpoint. Field policies provide fine-grained access control to you GraphQL API, using a model similar to attribute-based access control.
  • GraphQL configuration: You can use the config.yaml file to configure GraphQL settings, including enabling or disabling introspection and controlling the formatting of responses

To demonstrate these solutions, we will use the sample REST API available at https://sample-api.us-east-a.apiconnect.automation.ibm.com/api/customers and the following schema:

type Customer {
  email: String
  id: ID
  name: String
}
type Query {
  customers: [Customer]
    @rest(endpoint: "https://sample-api.us-east-a.apiconnect.automation.ibm.com/api/customers")
  customerById(id: ID!): Customer
    @rest(endpoint: "https://sample-api.us-east-a.apiconnect.automation.ibm.com/api/customers/$id")
  customersByQuery(q: String!): [Customer]
    @rest(endpoint: "https://sample-api.us-east-a.apiconnect.automation.ibm.com/api/customers")
}

We will be issuing curl requests in our examples. You can also try these variations out in API Connect for GraphQL Dashboard by toggling the custom headers are ON button in settings.

Admin keys and API keys

API Connect for GraphQL provides two different types of API keys for use with an account: Admin Keys and API Keys. Admin keys provide administrative-level access to your account, and should only be used at development time. API keys provide more limited access to your account, and should be used for production.

Using an Admin Key

Using an Admin key gives the caller full access to the schema and the queries or mutations in it, including diagnostics. An Admin key provides the caller the ability to:

  • create and update endpoints (for example, the schema and configuration)
  • execute any GraphQL operation, regardless of access control (field policies)
  • obtain diagnostics

An Admin key should only be used by people editing your schema, deploying GraphQL APIs, and debugging GraphQL APIs. It has performance implications when used to make a GraphQL request in production.

Issue the call by including the Authorization header in a request as follows:

-H "Authorization: Apikey YOUR_ADMIN_KEY"

For an example request using an Admin key with our schema, substitute YOUR_GRAPHQL_ENDPOINT into the following section.

curl YOUR_GRAPHQL_ENDPOINT \
--header "Authorization: Apikey $(stepzen whoami --adminkey)" \
--header "Content-Type: application/json" \
--data '{"query": "query customerSelection { customerById(id: 10) {email name}}"}'

Using the Admin key allows you to select any field from the entry point of the schema. In the example we selected the customerById field but you could also select any of the customers, customerById, or customersByQuery fields. Additionally, the Admin key allows you to request diagnostic information by using the Debugging Header.

Tip: $(stepzen whoami --adminkey) will substitute in your Admin key.

Using an API Key

To allow apps to execute operations (queries and mutations) on your endpoints, you can provide app developers with API keys to incorporate into their apps. The apps then call the GraphQL endpoint using an API key.

The key gives the app full access to the GraphQL API and the queries or mutations in it (but no diagnostics). The API key cannot be used to alter your schema.

Issue the call by including the Authorization header in a request:

-H "Authorization: Apikey YOUR_API_KEY"

The following example allows you to execute the Query operation with the customerById field with an API key. You could also select any of the customers, customerById, or customersByQuery fields.

curl YOUR_GRAPHQL_ENDPOINT \
--header "Authorization: Apikey $(stepzen whoami --apikey)" \
--header "Content-Type: application/json" \
--data '{"query": "query customerSelection { customerById(id: 10) {email name}}"}'
Tip: $(stepzen whoami --apikey) will substitute in your API key.

Use an API key (instead of an Admin key) to optimize runtime performance of your GraphQL requests.

API keys are a simple effective way to authenticate to your application. They are a good match for initial development because of the simplicity of use. However, they must be protected. You cannot simply hide the API key in browser code because anyone who opens the code will see it. To safely use API keys in this way requires that they be stored in an intermediary entity that will be between the browser and your GraphQL endpoint, which isn't always desirable or practical.

Using GraphQL settings

The config.yaml file supports configuration for GraphQL settings, including the ability to enable or disable introspection and control the formatting of responses.

deployment:
  graphql:
    introspection: false
    pretty: false
  • introspection - A boolean value to enable or disable GraphQL introspection.
    • true: Enables introspection (default behavior if not specified). Leaving introspection enabled in production can expose sensitive information and allow malicious parties to more easily discover vulnerabilities in the GraphQL schema.
    • false: Disables introspection, thereby enhancing the security of your GraphQL endpoint by preventing schema discovery.
  • pretty: A boolean value to control whether the GraphQL responses should be formatted with indentation and line breaks or not.
    • true: Enables formatting of responses, which is useful for development and debugging.
    • false: Disables formatting, resulting in compact responses that save bandwidth and improve performance.

Field policies

When it is not practical to store API keys in an intermediary entity, or when you need more granular control over access to your GraphQL API, field policies should be used. Field policies are a configuration-based mechanism that enables you to set rules for specific fields in the entry points of your GraphQL API.

Important: Requests made using a valid Admin key or API key will override any field policies configured for the endpoint allowing any field to be selected.

Field policies are specified in the config.yaml file (What's a config.yaml?). The following is an example of a field policy. In it, we provide rules which specify that the customerById field is accessible to any HTTP request (unauthenticated) and the customers field is accessible to any HTTP request that includes a valid JWT in the authorization header.

access:
  policies:
    - type: Query
      rules:
        - fields: [customerById]
          condition: true
        - fields: [customersByQuery]
          condition: '?$jwt'

If you add the config.yaml to your deployment, the following curl request will be denied since a valid JWT is not included in the request:

curl YOUR_GRAPHQL_ENDPOINT \
--header "Content-Type: application/json" \
--data '{"query": "query customerSelection { customersByQuery(query: \"id eq 10\") {email name}}"}'

However, the following curl request will be allowed since the Field Policy allows it:

curl YOUR_GRAPHQL_ENDPOINT \
--header "Content-Type: application/json" \
--data '{"query": "query customerSelection { customerById(id: 10) {email name}}"}'

For more details on the capabilities and syntax of field policies, see Using field policies.

For more details on using JWT with field policies, see Using JWT-based access control.

Unauthenticated Access with no Conditions

As shown in the preceding section, entry point fields can be made publicly accessible by creating a simple rule with a condition of true. If you want to provide completely open access to your endpoint -- for example if you were going to put a gateway in front of your endpoint, or it was an open endpoint -- you can add a policyDefault to your access policies with a condition of true and API Connect for GraphQL will allow requests to every field (unless otherwise specified in a rule).

access:
  policies:
    - type: Query
      policyDefault:
        condition: true

Replacing the previous config.yaml with the preceding section in our example will allow all three of the following requests to succeed:

curl YOUR_GRAPHQL_ENDPOINT \
--header "Content-Type: application/json" \
--data '{"query": "query customerSelection { customers {email name}}"}'
curl YOUR_GRAPHQL_ENDPOINT \
--header "Content-Type: application/json" \
--data '{"query": "query customerSelection { customerById(id: 10) {email name}}"}'
curl YOUR_GRAPHQL_ENDPOINT \
--header "Content-Type: application/json" \
--data '{"query": "query customerSelection { customersByQuery(query: \"id eq 10\") {email name}}"}'

Authenticated Access Using JWT

JWT (Java Web Token) based access helps restrict GraphQL operations subject to field policies. Follow the steps to enable authenticated access using a JWT:

  1. Add the following at the beginning of your config.yaml file:
deployment:
  identity:
    keys:
      - algorithm: HS256
        key: my-little-secret

The string my-little-secret can be anything you choose. As long as you issue JWTs with the key that you recorded, then the GraphQL using that JWT will be accepted as a valid call when received by your endpoint.

The HS256 algorithm is used here for demonstration only. We recommend that you use other algorithms in production.

  1. Update the access policies at the bottom of your config.yaml file to:
    access:
      policies:
        - type: Query
          policyDefault:
            condition: "?$jwt"
    
  2. Try this out by navigating to JWT.io and entering my-little-secret in the VERIFY SIGNATURE (and keep the PAYLOAD: DATA section as blank. For example, {}).
  3. Copy the encoded version which should look similar to the following:
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.K-4yYYM2eM2ex9WSlBtgphLkgQIdDfxr32yyW4EuMFM
    
  4. To verify that this works, issue a query to your endpoint with:
    -H "Authorization: Bearer YOUR-JWT" 

    adds the Bearer Authorization header:

    curl YOUR_GRAPHQL_ENDPOINT --header "Authorization: Bearer YOUR_JWT" --header "Content-Type: application/json" --data '{"query": "query customerSelection { customerById(id: 10) {email name}}"}'

    You can further verify that the token works, by changing the value of the token by one character causing the call to fail.

Use JWT Claims to Further Restrict Access

The JWT in the previous section was only used to check if the caller was authenticated. JWTs also support claims such as "I am user X".

Follow these steps to use JWT claims to further restrict access:

  1. Navigate to JWT.io and set PAYLOAD: DATA to be:
    {"user":"john.doe@example.com"}
    This generates a new JWT token like this:
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiam9obi5kb2VAZXhhbXBsZS5jb20ifQ.QCj6sKSgw08Ob2_-XOg6JMsGugWvRHxvZs7yQ_nStes
    
  2. Update your config.yaml file to contain the following policyDefault condition:
    policyDefault:condition:'$jwt.user: String == "john.doe@example.com"'
  3. Try the query to verify that it works successfully. Only the JWT token with that value of user in its PAYLOAD: DATA will be allowed. You can further verify that the claim works, by changing the value of user by one character causing the call to fail.

For more details on using JWT with field policies, see Using JWT-based access control.

Rules for keys

  • An Admin key allows you full rights on a schema, including create and update.
  • An API key has full access to all operation fields of a schema, but not ability to create and update the schema/
  • A access section in your config.yaml permits unauthenticated and JWT authenticated requests:
    • By setting rules, you can specify which selection fields of an operation can be accessed.
    • By setting policyDefault, you can control the selection of fields in an operation that are not listed in rules. You have access to $jwt (including claims in JWT) to set access conditions.
    • If you are using JWT, then you need to tell API Connect for GraphQL how to verify the correctness of JWT. That is accomplished through the deployment specification at the beginning of your config.yaml file.

Performance Considerations

Admin keys and API keys differ in the following important ways.

An Admin key is used for creating and deploying schemas, and for running GraphQL requests in trace and debug mode. As such, a request that uses an Admin key will fetch and compile the GraphQL schema from scratch, bypassing the query cache. This is so that, as a developer, you can be certain that you are seeing information about the latest version of a deployed endpoint.

On the other hand, a request that uses an API key is optimized for performance. Consequently, no debug or trace information is gathered. Errors and exceptions are not reported other than via the HTTP Status code. Most importantly, it will leverage a query cache whenever possible to avoid re-fetching and re-compiling the GraphQL query. This means that the performance of a request that uses an API key will be significantly better than the same request that uses an Admin key.

Note: The query cache is invalidated after 60 seconds, so if you change your schema, queries that use an API key might take up to a minute to notice and respond to the new model.