Article

Implementing authentication in GraphQL with OAuth 2.0 and IBM API Connect

Unlock the potential of OAuth 2.0 in GraphQL with IBM API Connect, from demystifying authentication flows to simplifying security implementation.

By

Roy Derks

You can use various methods for authentication in GraphQL, with OAuth 2.0 as one of the widely adopted methods. Specifically, OAuth 2.0 leverages JSON Web Tokens (JWT) or Client Credentials.

In this article, learn how to use OAuth 2.0 for authenticating GraphQL APIs through two distinct flows: the Authorization Code flow and the Client Credentials flow. Additionally, learn how to use IBM API Connect Essentials for authentication management.

What is OAuth 2.0?

OAuth 2.0 is an established open standard for authorization, enabling one application to grant another application access to specific portions of a user's account without giving away the user's password. The method of setting up this authorization, referred to as flows, varies depending on the type of application under development.

For example, if you are developing a mobile application, you can use the Authorization Code flow. This flow involves requesting the user's permission to access their account within the application. Subsequently, the application receives a code that it can exchange for an access token, often in the form of a JSON Web Token (JWT). This access token allows the application to retrieve the user's information from the website. You might have encountered this flow when logging in to a website through a social media platform such as Facebook or Twitter.

If you are building a server-to-server application, you can use the Client Credentials flow. In this scenario, the application sends unique credentials, such as a client ID and secret, to the website in exchange for an access token (JWT). This access token allows the server to access the user's data on the website. This flow is commonly employed by APIs that require access to a user's data, such as Customer Relationship Management (CRM) systems or marketing automation tools.

Now, let's delve deeper into these two distinctive flows.

Authorization code flow (using JWT)

A common approach to use OAuth 2.0 is through the Authorization Code flow, typically incorporating the use of JSON Web Tokens (JWT). As mentioned earlier, this flow is the preferred choice when building a mobile or web application that needs access to a user's data from a separate application.

For example, if you have developed a GraphQL API that enables users to retrieve their data, you can employ a JWT to verify the user's authorization for data access. The JWT might contain user-specific information, such as their unique user ID. The server can then leverage this user ID to query the database and furnish the user with their respective data.

Authorization Code flow using JWT

To accomplish this, you need a frontend application that is capable of guiding the user to the authorization server and subsequently redirecting them back to the frontend application with the authorization code. Subsequently, the frontend application can exchange this authorization code for an access token (JWT) and use that JWT to initiate requests to the GraphQL API.

The JWT can be transmitted to the GraphQL API by including it in the Authorization header and then the server can leverage the JWT to validate the user's authorization for data access:

curl https://YOUR_GRAPHQL_ENDPOINT \
   --header "Authorization: Bearer JWT_TOKEN" \
   --header "Content-Type: application/json" \
   --data-raw '{
     "query": "query { me { id username } }"
   }'

The JWT can contain additional details about the user's permissions such as their entitlement to access particular fields or mutations. This is useful when you want to impose restrictions on specific fields or mutations or when you want to limit the number of requests a user can initiate.

Client credentials flow

You can use the Client Credentials flow when you want to build a server-to-server application, such as an API, that requires access to data from another application. The Client Credentials flow also relies on JWT for authentication.

This flow involves the transmission of the website's distinct credentials, such as a client ID and secret, to get an access token. This access token allows the server to retrieve the user's information from the website. Unlike the Authorization Code flow, the Client Credentials flow doesn't involve a frontend client. Instead, the authorization server directly communicates with the server that needs access to user's data.

Client Credentials Flow

The JWT can be sent to the GraphQL API through the Authorization header similar to the method used in the Authorization Code flow.

In the following sections, learn how to implement both the Authorization Code flow and the Client Credentials flow by using API Connect Essentials.

Managing authentication with API Connect Essentials

API Connect Essentials (formerly StepZen) uses API keys as the default authentication mechanism for requests. This approach offers a developer-friendly method for authenticating requests that do not need an external authorization server. However, if you want to use OAuth 2.0 for request authentication, you can use API Connect Essentials for authentication management. Similar to how API Connect Essentials enables you to build a GraphQL schema for all your data in a declarative manner, you can also handle authentication declaratively.

Notice: StepZen was acquired by IBM in 2023 and renamed to API Connect Essentials. Some artifacts still use the StepZen name.

Implementing authorization code flow (using JWT)

To implement the Authorization Code flow, you must configure both a frontend client and an authorization server. You can use an existing authorization server, such as Auth0, or create your own.

API Connect Essentials performs JWT validation on tokens that are generated by the authorization server and then forwards them to the GraphQL API. The authorization server validates the user's credentials and generates a JWT, while the GraphQL server validates the JWT.

Let's revisit the described flow:

Authorization Code flow using JWT

The flow diagram shows the sequence of actions. The frontend application initiates the process by redirecting the user to the authorization server, in this case, Auth0. Subsequently, the user is redirected back to the frontend application with the authorization code. The frontend application then exchanges this authorization code for a JWT, which it subsequently uses to initiate requests to the GraphQL API.

To ensure security, API Connect Essentials performs JWT validation when the token is sent to the GraphQL API via the Authorization header. You can configure this validation by specifying the JSON Web Key Set (JWKS) endpoint in the config.yaml file of your project within API Connect Essentials:

deployment:
  identity:
    jwksendpoint: 'YOUR_JWKS_ENDPOINT'

The JWKS endpoint is a read-only resource that contains the public keys required for JWT validation. These public keys are exclusively used for token validation. To create and sign tokens, private keys are essential, which requires you to establish an authorization server to generate the JWTs.

Subsequently, you can enforce control over the fields and mutations that a user can access by incorporating Access Control rules into the GraphQL schema. As an illustration, you can introduce a rule for the me query, permitting access exclusively when a valid JWT is included with the request 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 permits access to the me query exclusively when a valid JWT is provided with the request to the GraphQL API. When the JWT is invalid or absent, invoking the me query will result in an error.

The JWT can carry additional information about to the user's permissions, including their eligibility to access particular fields or mutations. This functionality can be used to implement access restrictions on specific fields or mutations, or to limit the number of requests that a user can initiate.

For example, you can add a rule for the me query to grant access only to users with 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 by using API Connect Essentials, you can refer to this GitHub repository that contains the comprehensive code.

Implementing client credentials flow

To implement the Client Credentials flow, you must set up an authorization server. Unlike the Authorization Code flow, you do not need to redirect the user. Instead, the server directly communicates with the authorization server to get an access token (JWT).

Initially, you must configure an authorization server to generate the access token. You can use an existing authorization server, such as Auth0 or build your own authorization server.

In your API Connect Essentials project, you can configure the authorization server to generate the access token by modifying the config.yaml file:

# 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 authorization server requires specific parameters such as client_id, client_secret and audience to generate the access token (JWT). The audience represents the JWT's API identifier. The jwksendpoint is the same as the one used for the Authorization Code flow.

To get the access token, define a query in the .graphql file located in your API Connect Essentials project:

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 requests the authorization server to get the JWT. Within the postbody, you must include the parameters required by the authorization server to create the access token.

You can then use the JWT from the response of the token mutation to interact with the GraphQL API by sending the JWT in the Authorization header of the request.

Alternatively, you can use the @sequence custom directive to transfer the response from the token mutation to the query that needs authorization. This eliminates the need to manually include the JWT in the Authorization header with each individual 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 first initiates a request to the token query to get the JWT. Then, it sends a request to the "me" query, transmitting the JWT from the response of the token query as the access_token argument.

All of this configuration is consolidated in a single file and you can apply the same setup for both the Authorization Code flow and the Client Credentials flow. Both implementations follow a declarative approach, utilizing the identical JWKS endpoint to request the authorization server for token verification.

For a comprehensive example of implementing the Client Credentials flow including the GraphQL schemas, refer to the GitHub repository.

Summary

In this article, you have gained insights into commonly used OAuth 2.0 flows and how to implement them using IBM API Connect Essentials. Similar to other authentication mechanisms, the precise implementation details vary based on the specific requirements of your application and the required security measures.

GraphQL APIs built with API Connect Essentials are secure with an API key by default and offer flexibility for configuration with various authentication mechanisms. We're eager to hear about the authentication methods you employ with API Connect Essentials and the ways in which you apply them. Consider joining our Discord community to share your experiences and insights with us.