4. Authentication & Authorization
25m

Overview

In this module we will cover how to support JWT authentication and scope-based authorization through the .

By enabling these features on the , we can enforce security closer to the edge, reducing unnecessary compute at our and downstream services.

This module is split up into 2 sections: authentication and authorization.

Authentication

in focus: @authenticated

We'll be using Explorer to authenticate with an IdP (Identity Provider) to issue us a JWT (JSON Web Token) to send for subsequent calls.

To streamline this process, we've already provided a configured identity provider with Google Cloud Identity. The diagram below provides an overview of all the various steps that are happening through the lifecycle of the request.

Image showing the authentication flow

Step 1: Configuring the router

Let's edit the configuration (./router/router.yaml) to allow the to validate the JWT.

  1. Open up the ./router/router.yaml file in GitHub.

    GitHub view with router folder highlighted

    GitHub view with router.yaml file highlighted

  2. Copy the configuration below and paste it at the end of the router.yaml file:

    ./router/router.yaml
    ## Authentication and Authorization Module
    authentication:
    router:
    jwt:
    header_name: Authorization
    header_value_prefix: Bearer
    jwks:
    - url: https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com
    authorization:
    require_authentication: false
    preview_directives:
    enabled: true

    With this configuration, the will look for a Authorization header and extract the base64-encoded JWT after the Bearer prefix.

  3. Commit your changes.

Task!

Step 2: Update the schema to use @authenticated

The @authenticated can be marked on specific or types where authentication is required.

If the request is unauthenticated, the will remove any that are @authenticated before creating the .

Let's try authenticating the order(id: ID!) in the orders .

  1. Open the orders-schema.graphql file located in the root folder of the repository.

    GitHub workshop page with orders-schema.graphql file highlighted

  2. Add the @authenticated within your imports in the @link .

    ./products-schema.graphql
    extend schema
    @link(url: "https://specs.apollo.dev/federation/v2.8", import:
    [
    "@key",
    "@tag",
    "@shareable",
    "@authenticated"
    ]
    )
  3. Append the @authenticated to the root Query order(id: ID!).

    type Query {
    """
    Get a specific order by id. Meant to be used for a detailed view of an order
    """
    order(id: ID!): Order @authenticated
    }
  4. Commit your changes and they will be automatically published to .

It's that simple--no further changes were needed in the code!

Check your work: Testing the authentication policy

To keep things simple, we'll use Explorer with a pre-loaded preflight script that will handle the authentication for the client-side to pass over to the .

Explorer settings showing the pre-flight script

If you don't see this on your Explorer settings, take a look at the troubleshooting section below.

Enable the preflight script

  1. Select the cog icon in Explorer, and edit your Personal Settings to turn on the Preflight Scripts.

    Explorer settings showing how to turn on pre-flight script

Test a query without authentication headers

  1. Create a new tab for your by clicking the top + icon at the top of the Explorer tabs.

    https://studio.apollographql.com

    Studio view creating a new query tab

  2. Build a for the newly authentication-enforced with the order id set to "1". You can either build it using Explorer or copy and paste the below:

    query order($orderId: ID!) {
    order(id: $orderId) {
    id
    buyer {
    email
    firstName
    }
    items {
    price
    size
    colorway
    id
    }
    }
    }
  3. In the Variables panel, copy and paste the following JSON payload:

    { "orderId": "1" }
  4. Execute the with the Play button in the top-right corner.

  5. As expected, you will get an error as the result of this : UNAUTHORIZED_FIELD_OR_TYPE.

    https://studio.apollographql.com

    Results view showing the unauthenticated error

Test a query with authentication headers

To make an authenticated request, let's add an authorization header with an authenticated JWT.

  1. Click + New Header in the Headers panel located next to Variables.

    Location of the New Header button in the Headers panel

  2. Use the following key-value for the new header:

    HeaderValue
    AuthorizationBearer {{authorizedToken}}
  3. Run the again. In this case, we should see that data is returned as expected!

    https://studio.apollographql.com

    Results view showing the authenticated success

Authorization - scope-based

in focus: @requiresScope

After a JWT request has been validated, we will leverage the scopes within the JWT to determine what the request is authorized to access.

This gives an advantage over REST as we can define authorization policies at the individual level, allowing for flexibility and reusability.

The @requireScopes has a parameter called scopes, which takes an array of array of scopes, meaning you can require a different combination of scopes. For example:

  • requiresScopes(scopes: [["order:items"], ["order:buyer"]]): either "order:item" or "order:buyer" would work
  • requiresScopes(scopes: [["order:items", "order:buyer"]]): require both scopes to be present

Step 1: Update the schema to use @requiresScopes

Following a least-privileged approach, our scopes will be read-only by default. The table below outlines the two scopes we want to enforce.

HeaderValue
order:buyerRead-only access to the purchaser or owner of the order.
order:itemsRead-only access to the items associated with an order.

Let's add the @requiresScopes to enforce scope-based authorization within the .

  1. Open the orders-schema.graphql file located in the root folder of the repository:

    https://github.com

    GitHub view with orders-schema.graphql file highlighted

  2. Add the @requiresScope within your imports in the @link .

    ./orders-schema.graphql
    extend schema
    @link(url: "https://specs.apollo.dev/federation/v2.8", import:
    [
    "@key",
    "@tag",
    "@shareable",
    "@authenticated",
    "@requiresScopes",
    ]
    )
  3. Append the @requiresScopes to the Order type's buyer and items .

    ./orders-schema.graphql
    type Order @key(fields: "id") {
    """
    Each order has a unique id which is separate from the user or items they bought
    """
    id: ID!
    """
    The user who made the purchase
    """
    buyer: User! @requiresScopes(scopes: [["order:buyer"]])
    """
    A list of all the items they purchased. This is the Variants, not the Products so we know exactly which
    product and which size/color/feature was bought
    """
    items: [ProductVariant!]! @requiresScopes(scopes: [["order:items"]])
    }
  4. Commit your schema changes.

Check your work: Testing the authorization policy

Set up the operation

Let's make a request to retrieve an order's buyer and items.

  1. Create a new tab and copy the below:

    query order($orderId: ID!) {
    order(id: $orderId) {
    id
    buyer {
    email
    firstName
    }
    items {
    price
    size
    colorway
    id
    }
    }
    }
  2. In the Variables panel, copy and paste the following JSON payload:

    { "orderId": "1" }

Testing an unauthorized request

  1. In the Headers panel, set the Authorization to an unauthorized token by setting the value to Bearer {{unauthorizedToken}}.

    Bearer {{unauthorizedToken}}

    We should expect that these values we don't have access to return as null, despite being a valid JWT.

  2. Run the . You should get errors with Unauthorized field or type along with the path to which are restricted: Order.buyer and Order.items.

    https://studio.apollographql.com

    Explorer results view showing the unauthorized error

Testing an authorized request

We need to add a new authorization header that will reference a different token: one created by our preflight script. Currently our token does not have the correct scopes to access the data. Let's set our header with a token that does.

  1. Edit the Authorization header you created previously. Set the value to Bearer {{authorizedToken}}.

    Bearer {{authorizedToken}}
  2. Send the request again. We should see that data has successfully been returned!

https://studio.apollographql.com

Results view showing the authenticated data

Authentication and authorization checklist

Up next

In this module, we've covered how to support JWT authentication and scope-based authorization through the .

In the next section, we'll show how we can leverage the power of coprocessors to protect PII data in our .

Previous