Authenticating GraphQL APIs with OAuth 2.0
There are many different ways to handle authentication in GraphQL, but one of the most common is to use OAuth 2.0 - and, more specifically, JSON Web Tokens (JWT) or Client Credentials.
In this blog post, we'll look at how to use OAuth 2.0 to authenticate GraphQL APIs using two different flows: the Authorization Code flow and the Client Credentials flow. We'll also look at how to use StepZen to manage authentication.
What is OAuth 2.0?
But first, what is OAuth 2.0? OAuth 2.0 is an open standard for authorization that allows one application to let another application access certain parts of a user's account without giving away the user's password. There are different ways to set up this type of authorization, called "flows", and it depends on the type of application you are building.
For example, if you're building a mobile app, you will use the "Authorization Code" flow. This flow will ask the user to permit the app to access their account, and then the app will receive a code to use to get an access token (JWT). The access token will allow the app to access the user's information on the website. You might have seen this flow when you log in to a website using a social media account, such as Facebook or Twitter.
Another example is if you're building a server-to-server application, you will use the "Client Credentials" flow. This flow involves sending the website's unique information, like a client ID and secret, to get an access token (JWT). The access token will allow the server to access the user's information on the website. This flow is quite common for APIs that need to access a user's data, such as a CRM or a marketing automation tool.
Let's have a look at these two flows in more detail.
Authorization Code Flow (using JWT)
The most common way to use OAuth 2.0 is with the Authorization Code flow, which involves using JSON Web Tokens (JWT). As mentioned above, this flow is used when you want to build a mobile or web application that needs to access a user's data from a different application.
For example, if you have a GraphQL API that allows users to access their data, you can use a JWT to verify that the user is authorized to access the data. The JWT could contain information about the user, such as the user's ID, and the server can use this ID to query the database and return the user's data.
You would need a frontend application that can redirect the user to the authorization server and then redirect the user back to the frontend application with the authorization code. The frontend application can then exchange the authorization code for an access token (JWT) and then use the JWT to make requests to the GraphQL API.
The JWT can be sent to the GraphQL API in the Authorization
header:
curl https://USERNAME.stepzen.net/api/hello-world/__graphql \
--header "Authorization: Bearer JWT_TOKEN" \
--header "Content-Type: application/json" \
--data-raw '{
"query": "query { me { id username } }"
}'
And the server can use the JWT to verify that the user is authorized to access the data.
The JWT can also contain information about the user's permissions, such as whether they can access a specific field or mutation. This is useful if you want to restrict access to specific fields or mutations or if you want to limit the number of requests a user can make. But we'll look at this in more detail after discussing the Client Credentials flow.
Client Credentials Flow
The Client Credentials flow is used when you want to build a server-to-server application, like an API, that needs to access information from a different application. It also relies on JWT.
As mentioned above, this flow involves sending the website's unique information, like a client ID and secret, to get an access token. The access token will allow the server to access the user's information on the website. Unlike the Authorization Code flow, the Client Credentials flow doesn't involve a (frontend) client. Instead, the authorization server will directly communicate with the server that needs to access the user's information.
Image from Auth0
The JWT can be sent to the GraphQL API in the Authorization
header, in the same way as for the Authorization Code flow.
In the next section, we'll look at how to implement both the Authorization Code flow and the Client Credentials flow using StepZen.
Using StepZen to Manage Authentication
By default, StepZen uses API Keys to authenticate requests. This is a developer-friendly way to authenticate requests that don't require an external authorization server. But if you want to use OAuth 2.0 to authenticate requests, you can use StepZen to manage authentication. Similar to how you can use StepZen to build a GraphQL schema for all your data in a declarative way, you can also manage authentication declaratively.
Implement Authorization Code Flow (using JWT)
To implement the Authorization Code flow, you must set up both a (frontend) client and an authorization server. You can use an existing authorization server, such as Auth0, or build your own.
You can find a complete example of using StepZen to implement the Authorization Code flow in the StepZen GitHub repository.
StepZen can validate the JWTs generated by the authorization server and send them to the GraphQL API. You only need the authorization server to validate the user's credentials to generate a JWT and StepZen to validate the JWT.
Let's have another look at the flow we discussed above:
In this flow diagram, you can see that the frontend application redirects the user to the authorization server (from Auth0) and then turns the user back to the frontend application with the authorization code. The frontend application can then exchange the authorization code for a JWT and then use that JWT to make requests to the GraphQL API.
StepZen will validate the JWT that is sent to the GraphQL API in the Authorization
header by configuring the JSON Web Key Set (JWKS) endpoint in the StepZen configuration in the config.yaml
file in your project:
deployment:
identity:
jwksendpoint: 'YOUR_JWKS_ENDPOINT'
The JWKS endpoint is a read-only endpoint that contains the public keys to verify a JWT. The public keys can only be used to validate the tokens, as you would need the private keys to sign the tokens, which is why you need to set up an authorization server to generate the JWTs.
You can then limit the fields and mutations a user can access by adding Access Control rules to the GraphQL schema. For example, you can add a rule to the me
query to only allow access when a valid JWT is sent to the GraphQL API:
deployment:
identity:
jwksendpoint: 'YOUR_JWKS_ENDPOINT'
access:
policies:
- type: Query
rules:
- condition: '?$jwt' # Require JWT
fields: [me] # Define fields that require JWT
This rule only allows access to the me
query when a valid JWT is sent to the GraphQL API. If the JWT is invalid, or if no JWT is sent, the me
query will return an error.
Earlier, we mentioned that the JWT could contain information about the user's permissions, such as whether they can access a specific field or mutation. This is useful if you want to restrict access to specific fields or mutations or if you want to limit the number of requests a user can make.
You can add a rule to the me
query to only allow access when a user has the admin
role:
deployment:
identity:
jwksendpoint: 'YOUR_JWKS_ENDPOINT'
access:
policies:
- type: Query
rules:
- condition: '$jwt.roles:String has "admin"' # Require JWT
fields: [me] # Define fields that require JWT
To learn more about implementing the Authorization Code Flow with StepZen, look at the Easy Attribute-based Access Control for any GraphQL API article on the StepZen blog.
Implement Client Credentials Flow
You will also need to set up an authorization server to implement the Client Credentials flow. But instead of redirecting the user to the authorization server, the server will directly communicate with the authorization server to get an access token (JWT).
You can find a complete example for implementing the Client Credentials flow in the StepZen GitHub repository.
First, you must set up the authorization server to generate the access token. You can use an existing authorization server, such as Auth0, or build your own.
In the config.yaml
file in your StepZen project, you can configure the authorization server to generate the access token:
# Add the JWKS endpoint
deployment:
identity:
jwksendpoint: 'https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json'
# Add the authorization server configuration
configurationset:
- configuration:
name: auth
client_id: YOUR_CLIENT_ID
client_secret: YOUR_CLIENT_SECRET
audience: YOUR_AUDIENCE
The client_id
, client_secret
and audience
are required parameters for the authorization server to generate the access token (JWT). The audience
is the API's identifier for the JWT. The jwksendpoint
is the same as the one we used for the Authorization Code flow.
In a .graphql
file in your StepZen project, you can define a query to get the access token:
type Query {
token: Token
@rest(
method: POST
endpoint: "YOUR_AUTHORIZATION_SERVER/oauth/token"
postbody: """
{
"client_id": "{{ .Get "client_id" }}",
"client_secret": "{{ .Get "client_secret" }}",
"audience": "{{ .Get "audience" }}",
"grant_type": "client_credentials"
}
"""
)
}
The token
mutation will request the authorization server to get the JWT. The postbody
contains the parameters that are required by the authorization server to generate the access token.
You can then use the JWT from the response on the token
mutation to request the GraphQL API, by sending the JWT in the Authorization
header.
But we can do better than that. We can use the @sequence
custom directive to pass the response of the token
mutation to the query that needs authorization. This way, we don't need to send the JWT manually in the Authorization
header on every request:
type Query {
me(access_token: String!): User
@rest(
endpoint: "YOUR_API_ENDPOINT"
headers: [{ name: "Authorization", value: "Bearer $access_token" }]
)
profile: User @sequence(steps: [{ query: "token" }, { query: "me" }])
}
The profile
query will first request the token
query to get the JWT. Then, it will send a request to the me
query, passing along the JWT from the response of the token
query as the access_token
argument.
As you can see, all configuration is set up in a single file, and you can use the same configuration for both the Authorization Code flow and the Client Credentials flow. Both are written declarative, and both use the same JWKS endpoint to request the authorization server to verify the tokens.
What's next?
In this blog post, you learned about common OAuth 2.0 flows and how to implement them with StepZen. It's important to note that, as with any authentication mechanism, the details of the implementation will depend on the application's specific requirements and the security measures that need to be in place.
StepZen GraphQL APIs are default protected with an API key but can be configured to use any authentication mechanism. We'd love to hear what authentication mechanisms you use with StepZen and how you use them. Ping us on Twitter or join our Discord community to let us know.
This post was originally published on stepzen.com. Reposted automatically with Reposted.io.