StepZen is now part of IBM. For the most recent product information and updates go to
https://www.ibm.com/products/stepzen

Secure Schemas and Endpoints

How to control access to GraphQL schemas and endpoints

As you build out your GraphQL APIs, you will want to add access control mechanisms to prevent unauthorized use of these endpoints. StepZen supports two different solutions for this problem:

  • API Keys: You can use 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.

To demonstrate these solutions, we will use the sample REST API available at https://introspection.apis.stepzen.com/customers and the following schema:

type Customer {
  email: String
  id: ID
  name: String
}
type Query {
  customers: [Customer]
    @rest(endpoint: "https://introspection.apis.stepzen.com/customers")
  customerById(id: ID!): Customer
    @rest(endpoint: "https://introspection.apis.stepzen.com/customers/$id")
  customersByQuery(q: String!): [Customer]
    @rest(endpoint: "https://introspection.apis.stepzen.com/customers")
}

and we will be issuing curl requests in our examples.

Tip: You can also try these variations out via GraphiQL editor by toggling the Rulesets / custom headers are ON button in settings.

API Keys

StepZen 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.

Use 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 (e.g. 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 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 a example request using an Admin key with our schema, substitute YOUR_GRAPHQL_ENDPOINT into the below.

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. See Debugging Header.

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

Use an API Key

To allow apps to execute operations (queries and mutations) on your endpoint(s), 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"

Similar to above, the below 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: Note that $(stepzen whoami --apikey) will substitute in your API key.

Important: Use an API key, not 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.

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.

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 customerByQuery field is accessible to any http request that includes a valid JWT in the authorization header. Since calls made with valid API keys will override field policies, the customerByQuery field will only be accessible to a request made with a valid API key.

access:
  policies:
    - type: Query
      rules:
        -
          fields: customerById
          condition: true
        -
          fields: customersByQuery
          condition: "?$jwt"

If you add the above config.yaml to your StepZen 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 Field Policies.

For more details on using JWT with field policies, see JWT-based Access.

Unauthenticated Access with no Conditions

As shown above, 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 StepZen 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 one above 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 a JWT

JWT based access helps restrict GraphQL operations subject to field policies. Follow the steps below to enable authenticated access using a JWT:

  1. Add the following at the top of your config.yaml:

    deployment:
      identity:
        keys:
          - algorithm: HS256
            key: my-little-secret

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

    Note: HS256 is used here for demonstration only. You can, and we recommend, you use other algorithms in production.

  2. Update the access policies at the bottom of your config.yaml to:

    access:
      policies:
      - type: Query
        policyDefault:
          condition: "?$jwt"
  3. Try this out by navigating to JWT.io and entering my-little-secret in the VERIFY SIGNATURE on the right side (and keep the PAYLOAD: DATA section as blank. For example, {}).

  4. Copy the encoded version on the left which should look similar to the following:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.K-4yYYM2eM2ex9WSlBtgphLkgQIdDfxr32yyW4EuMFM
  5. To verify that this works, issue a query to your endpoint with

    -H "Authorization: Bearer YOUR-JWT"

    To see this work in our running example, try the following curl command that 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 like "I am user X".

Follow the steps below 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. Change your config.yaml to contain the following policyDefault condition:

    policyDefault:
       condition: '$jwt.user: String == "john.doe@example.com"'
  3. Try out 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 JWT-based Access.

Overall Rules

  1. An Admin key allows you full rights on a schema, including create and update.

  2. An API key has full access to all operation fields of a schema, but not ability to create and update the schema/

  3. 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 StepZen how to verify the correctness of JWT. That is accomplished through the deployment specification at the beginning of your config.yaml (as described above).

Performance Considerations

As this section has conveyed, there are two types of keys that you can use to authenticate requests to StepZen: Admin keys and API keys. These keys differ in some 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 may take up to a minute to notice and respond to the new model.