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:
-
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. -
Update the
access
policies at the bottom of yourconfig.yaml
to:access: policies: - type: Query policyDefault: condition: "?$jwt"
-
Try this out by navigating to JWT.io and entering
my-little-secret
in theVERIFY SIGNATURE
on the right side (and keep thePAYLOAD: DATA
section as blank. For example,{}
). -
Copy the encoded version on the left which should look similar to the following:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.K-4yYYM2eM2ex9WSlBtgphLkgQIdDfxr32yyW4EuMFM
-
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:
-
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
-
Change your
config.yaml
to contain the following policyDefault condition:policyDefault: condition: '$jwt.user: String == "john.doe@example.com"'
-
Try out the query to verify that it works successfully. Only the JWT token with that value of
user
in itsPAYLOAD: DATA
will be allowed. You can further verify that the claim works, by changing the value ofuser
by one character causing the call to fail.
For more details on using JWT with field policies, see JWT-based Access.
Overall Rules
-
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 yourconfig.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 inrules
. 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 yourconfig.yaml
(as described above).
- By setting
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.