GraphQL Group Based Access Control

access-controlgraphqlgrafbaseauthdogsecurity

Mon Aug 14 2023

David Barrat
Enable Single-Sign-On and Group-based-access-control in a Next.js app with a Grafbase API using Grafbase Auth capabilities and Authdog IdP

Context

Grafbase is a platform to enable developers to ship faster GraphQL APIs on the Edge and simplify connectivity with 3rd party technologies as a federated GraphQL endpoint.
Authdog is a Customer Identity and Access Management platform to centralize Identity and access control management for Web Applications/APIs/Backend services.
Grafbase offers granular access control compatible with external Identity Providers.
This guide will walk through the steps to get started with Authdog and Grafbase Auth, to get in minutes a functional Next.js connected securely to a backend, with groups managed in the Authdog platform and session token validated on Grafbase. The code for this example is available in this repository: https://github.com/authdog-samples/grafbase-auth

Technologies used:

  • Codebase: Turborepo
  • Frontend application: Next.js / Tailwind starter
  • Backend service: Grafbase SDK/API
  • Authentication as a service: Authdog SDK/API


Application creation

To enable users to authenticate in the front-end application, the first step consists in registering an Application in Authdog Console Dashboard, first thing to do is to follow console.authdog.com and create an account.
You’ll be invited to create a Tenant, once done, you’ll see a Page like this one:



To get to the new Application form, click on the
New Application
button on the top right. You’ll be redirected to a multi-step form, to define basic information about your project, respectively naming your application, adding connections to it, and defining redirections.



Once you’ve defined the basic information of your project, you’ll see a list of providers.



Simply click on the gear button, and you’ll see a form to configure your provider.



Go to your Identity Provider to figure out where to find your Client ID, Client Secret obtained when creating an OAuth2 Client; paste the Redirect URI copied from this form into your provider-authorized URI for your connection.

Note that the Redirect URI is unique per provider, connection and per environment


Once ready, click on Save.

To have your Login Form working, you need to define a Redirection after successful login, for your local instance, it can be localhost:3000/dashboard to be the path the user will be redirected to after a successful Authentication. Click on
Save
once ready.



Now that we’ve configured our Application in Authdog Console, we’ll need to create a Grafbase Project. We’ll use the Command Line Interface (CLI) to do so.

Grafbase Project creation



To create a Grafbase project from the CLI, you’ll need to pass a name, account and region to the `grafbase create` target.



Grafbase TypeScript configuration



Now that the project is created we’ll define our Grafbase configuration and a protected resolver. First, we import g, auth, config being respectively schema builder, auth configuration builder and grafbase configuration builder.

1 import { g, auth, config } from '@grafbase/sdk'


Then we’ll instantiate the auth configuration builder

1 2 3 4 const authdog = auth.OpenIDConnect({ issuer: "https://id.auth.dog", groupsClaim: "groups", });


We’ve created an Authdog auth config object for an OpenID configuration, with the issuer being `id.auth.dog (iss field in the token claims, and must have a jwks endpoint at https://id.auth.dog/.well-known/jwks.json). By default, when you’re creating an application in Authdog without creating signing keys, the default ones from the Authdog region will be used to sign your JSON Web Tokens. groupsClaim is the claim in your JWT that self-contains your user’s groups. It can be configured in Authdog JWT Manager.

Then we’ll create a resolver, in our Grafbase project in `grafbase/resolvers/hello-world.ts`. The goal is to enable access to this resolver to a user with specific group membership.

1 2 3 export default async function Resolver(_, args, context) { return "Hello world!"; }


We’ll reference this resolver in our grafbase configuration, it’ll be `helloWorld` query in our schema.

1 2 3 4 5 g.query("helloWorld", { args: {}, returns: g.string(), resolver: "hello-world", });


Finally, we’ll define our auth block for the schema

1 2 3 4 5 6 const authParams = { providers: [authdog], rules: (rules) => { rules.groups([]); }, };


Our auth block takes two parameters:
  • providers, being the list of auth providers for the schema
  • rules, being the rules applied to the schema
    To enable access to a given group, you pass its id in the rules goups array parameter. Using a uuid is a deliberate choice to allow group renaming in the Authdog dashboard without breaking the access-control logic at scale.


Our config will look like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import { g, auth, config } from "@grafbase/sdk"; const authdog = auth.OpenIDConnect({ issuer: "https://id.auth.dog", groupsClaim: "groups", }); g.query("helloWorld", { args: {}, returns: g.string(), resolver: "hello-world", }); const authParams = { providers: [authdog], rules: (rules) => { // We will populate allowed groups later rules.groups([]); }, }; export default config({ schema: g, auth: authParams, });


JWT group claims configuration



Add groups claims field



Go to console.authdog.com/jwt-manager, and make sure the environment is the same as the one used for connecting an SSO identity provider. The group claim field must match the one defined in the Grafbase configuration.



After saving, you’ll see the claims preview updated:



Now we’ll now enable Authentication in our app.

Frontend App configuration



React SDK in Next.js



Install dependencies

1 pnpm add @authdog/react @authdog/browser @authdog/shared
Once the dependencies are installed, we’ll add AuthdogProvider to our app.

Next.js 12 (App Router will be soon supported)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // pages/_app.tsx import type { AppProps } from "next/app"; import { AuthdogProvider } from "@authdog/react"; export default function App({ Component, pageProps }: AppProps) { const authnUri = "<copied from application configure dashboard in settings/endpoints>"; const webLoginUri = "<copied from application configure dashboard in settings/endpoints>"; return ( <AuthdogProvider authnApi={authnUri} webLoginUri={webLoginUri} {...pageProps} > <Component {...pageProps} /> </AuthdogProvider> ); }


You can grab the parameters from Authdog Console Dashboard, in Application configuration:



Once those parameters are populated, your application is ready to authenticate users.

Local Next.js



Next.js project can be started with the make dev command. Here’s the view you’ll see once the project is started locally:



After pressing Access Dashboard, you’ll be redirected to a Login form (managed in Authdog Cloud Console) with the Auth provider we previously configured in Authdog Console.



Finally, after authenticating, you should see this screen with the authenticated user:



Groups management



Let’s move on to Authdog Access Control: console.authdog.com/access-control

You’ll see a list of users in the first tab, corresponding to the users having logged into your application, for the given environment.



By default, you don’t have any group, so go to the groups tab, click on Create Group to add a new group to your environment, populate your group information and once ready, click on Save Group.



Go back to the Users tab, and click on “Manage Groups for Selection”, a new modal will appear with a transfer list. On the left, users are available and on the right users have been added to the selected group.



Use the checkboxes to select your users on the left, and click on the center arrow to transfer them to the right



The user on the right panel belongs now to the “Grafbase Granted Group”.

This user claim in the JWT will be something like this below, note the groups claim having populated.

1 2 3 4 5 6 7 8 9 10 { "externalid": "google-oauth20:100518665645689882436", "sub": "google-oauth20:100518665645689882436:23c0ed12-444f-4b2d-8afa-e9413e51f29f", "iss": "https://id.auth.dog", "iat": 1691966733, "exp": 1691970333, "groups": [ "cdab03cd-a442-4564-8c6c-307ce90a2f74" ] }


Now let’s copy this group’s id to add it to the Grafbase configuration.



1 2 3 4 5 6 7 8 9 10 11 12 const authParams = { providers: [authdog], rules: (rules) => { // cdab03cd-a442-4564-8c6c-307ce90a2f74 -> Grafbase granted group rules.groups(["cdab03cd-a442-4564-8c6c-307ce90a2f74"]); }, }; export default config({ schema: g, auth: authParams, });


This means now, users authenticating with Authdog, having been assigned to this group, will be able to access Grafbase services with a Single-Sign-On.

Note that once users are authenticated, and the token embeds the groups, you won’t be able to change ownership dynamically with this setup, it can be achieved by revoking the current signing key and forcing the user to reauthenticate to have the new group added to the claims, after authenticating again.


You’ll need to deploy your Grafbase API whenever you change the configuration.

Grafbase Integration in Next.js application



Now that we’ve got a group configured with a user, we’ll configure our front-end application to access our group secured query from our Grafbase API. The token provided by Authdog API will be used to access our Grafbase backend.

To simplify this guide, we won’t get through the whole process of configuring a GraphQL client library, but instead, we’ll do a call via native VanillaJS JavaScript fetch through a basic wrapper.

Here’s our GraphQL client wrapper code:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 export const fetchGraphQL = async ({ endpoint, query, authorization, }: IFetchGraphQL) => { return await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${authorization}`, }, body: JSON.stringify({ query }), }); };


We’ll replace the endpoint parameter, with the endpoint previously created with Grafbase CLI and query parameter with `helloWorld` query. Invocation from the front-end application would be something like:

1 2 3 4 5 6 7 8 9 10 11 12 fetchGraphQL({ endpoint: "https://grafbase-auth-authdog.grafbase.app/graphql", query: `{helloWorld}`, authorization: `Bearer ey...`, }) .then((response) => response.json()) // Parse the JSON response .then((data) => { console.log(data); }) .catch((error) => { console.error("Error fetching GraphQL data:", error); });


After having integrated this wrapper, we can now test the result with our protected endpoint.

User A has the required group membership:



User B doesn’t have the required group membership:



Conclusion

In this guide, we’ve surfaced the Authdog Console user interface for Application, Connection and Access-Control Group management, leveraged Grafbase native Auth capabilities, and demonstrated the synergy between the two platforms and the power/value of managed Edge-based services. The source code for this project is available at: https://github.com/authdog-samples/grafbase-auth

Get Started

What is Authdog?

Logo

© 2023 Authdog LLC. All rights reserved.