Securing a GraphQL Server API with ASP.NET Core & Azure AD B2C - Part 1

Create the API

The Setup

In this blog series, I'm going to cover the process of standing up an API using GraphQL, GraphiQL and ASP.NET Core. Once the API is up and running, I'll show how to secure the API using Azure AD B2C. Then I'll discuss how to get the solution built and deployed to an Azure App Service using Azure DevOps pipelines. Finally, we'll take a look at how to integrate the newly secured API with a React App to customize the user experience.

The series assumes that you know basic GraphQL concepts, and does not cover building queries, mutations, etc except as they relate to or are effected by the particulars of the security implementation. I also assume you know your way around the Azure Portal and Azure DevOps. All series code will be made available on GitHub. I'll be using Visual Studio Code throughout the series, feel free to use the tool of your choice to manage your code.

Part 1 - The Phantom API

Part 1 of the series will cover getting the baseline API and GraphiQL UI setup and running locally on your development machine. Later parts will build on this baseline to add security, automated builds, etc.

Baseline Project & Packages

To get started, we'll create a new empty ASP.NET Core project. Open Visual Studio Code in the folder for your solution, then open a new Terminal (Terminal -> New Terminal).

...>dotnet new web --name StarWars.API --output api
The template "ASP.NET Core Empty" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on api\StarWars.API.csproj...
  Determining projects to restore...
  Restored ...\graphql-aspnetcore-azureadb2c\api\StarWars.API.csproj (in 96 ms).
Restore succeeded.

...>

Once the command completes, your project should look something like this:

Starting Project StateStarting Project State

Now, we'll need to add some NuGet packages to the project, add the following packages to get started (via dotnet add package [name]):

  • GraphQL
  • GraphQL.MicrosoftDI
  • GraphQL.Server.Core
  • GraphQL.SystemTextJson
  • GraphQL.Server.Transports.AspNetCore
  • GraphQL.Server.Transports.AspNetCore.SystemTextJson
  • GraphQL.Server.Transports.Subscriptions.WebSockets
  • GraphQL.Server.Ui.GraphiQL

Add the GraphQL Schema

The GraphQL API isn't going to go anywhere without a schema to publish for queries, mutations, etc. I'll base our schema off the sample 'StarWars' schema/types published here StarWars Schema. For this sample, we won't have an actual data source, but we will separate our 'data model' and 'graphql model'. We'll add these types to the 'Models' and 'Schema' folders of our project. In order to supply our API with data, we'll create a basic in-memory data store similar to the one used in the example code above. We'll place this StarWarsDataService into the 'Services' folder of our project.

StarWars Schema TypesStarWars Schema Types

I know, I didn't walk through all the steps to create these files and the alterations I made to the base example, but that really isn't the focus here. I'll leave those changes as an exercise for you, as each of you will likely have your own opinions on code structure, etc that you can apply to the sample code. If you just want to follow along, feel free to jump out to the GitHub Repo and find the model/schema/service files in the 'part-1' branch! Make sure you run a dotnet build at this point to make sure everything builds successfully.

Great, that's ready to go! Let's move on to getting the ASP.NET Core side of the house configured.

Wire up GraphQL to the ASP.NET Core runtime

Now that we the required packages and code added to the project, it's time to wire things together for the ASP.NET Core runtime to expose our GraphQL API and enable our GraphiQL user interface. All the work here happens in the Startup.cs file. Let's start with the ConfigureServices method:

    // Application Services
    services.AddSingleton<IStarWarsDataService, StarWarsDataService>();

    // Schema
    services.AddScoped<IDocumentWriter, GraphQL.SystemTextJson.DocumentWriter>();
    services.AddScoped<StarWarsSchema>(services => new StarWarsSchema(new SelfActivatingServiceProvider(services)));

    // GraphQL
    services.AddGraphQL()
            .AddDefaultEndpointSelectorPolicy()
            .AddSystemTextJson()
            .AddWebSockets()
            .AddGraphTypes(ServiceLifetime.Scoped);

Go to GitHub

A couple things to mention here, we need to tell GraphQL to use the SystemTextJson variant of the document writer to match with our chosen ASP.NET Core transport. This variant is necessary because the default Newtonsoft variant doesn't fully support asynchronous execution (at least at this time). The SelfActivatingServiceProvider implementation is used to avoid manually adding all our schema types individually, we just pass in the schema and the rest auto-registers with the service provider as needed. Lastly, because GraphiQL uses Web Sockets (and we'll eventually want to add subscriptions), we need to add the AddDefaultEndpointSelectorPolicy() call to the GraphQL setup chain, otherwise the web socket connections won't get handled properly.

Alright now on to a matching set of updates to the Configure method.

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();
    app.UseWebSockets();

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        // GraphQL
        endpoints.MapGraphQLWebSockets<StarWarsSchema>();
        endpoints.MapGraphQL<StarWarsSchema>();

        // GraphiQL
        endpoints.MapGraphQLGraphiQL();
    });

Go to GitHub

This setup is a little simpler, just needing to make sure Web Sockets are enabled, and ensuring that the 'endpoint compatible' variants of the GraphQL methods are placed inside the UseEndpoints call. I've also added code to force HTTPS, as that's a best practice for most APIs, but feel free to leave that out if desired.

Startup and Debugging

If you're anything like I am, very few things go to plan right out of the gate, so having the ability to debug your solution is very handy. In VS Code, we can enable this with just a couple tweaks. It feels like you should be able to just set the launchUrl property within the Properties\launchSettings.json file that was created as part of the initial project setup, but this doesn't actually work as of this writing. So, for now, we're going to disable the automated browser launch that's part of this section.

// Properties\launchSettings.json
{
  "iisSettings": {
      // ...
  },
  "profiles": {
    "IIS Express": {
        // ...
    },
    "StarWars.API": {
      "commandName": "Project",
      "dotnetRunMessages": "true",
      /* Disable this for now, until this works properly */
      "launchBrowser": false,
      /* This feels like it should work... but it doesn't (yet)
      "launchBrowser": true,
      "launchUrl": "ui/graphiql",
      */
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Go to GitHub

Now, clicking on the 'Run and Debug' section of VS Code should present you with a prompt to create a launch.json file. We want to tell the system to launch our /ui/graphiql page on startup, so we can't use the default configurations. Select the '.NET Core' option when asked, then open the resulting .vscode\launch.json file and add the following setting into the "name": ".NET Core Launch (web)" section.

// .vscode\launch.json
{
    // ...
    "configurations": [
        {
            "name": ".NET Core Launch (web)",
            // ...
            "serverReadyAction": {
                "action": "openExternally",
                "pattern": "\\bNow listening on:\\s+(https?://\\S+)",
                /* Add the following line to launch the GraphiQL UI on debug */
                "uriFormat": "%s/ui/graphiql"
            },
            // ...
        },
        // ...
    ]
}

Go to GitHub

With that done, set a breakpoint in the Services\StarWarsDataService.cs constructor and hit the 'Play' button in the debug area. This should launch the browser into GraphiQL (you may have to bypass the SSL certificate warning). The replace the query with the following:

query {
  hero {
    id
    name
    friends {
      name
    }
  }
}

Hit 'Run' in GraphiQL and you should hit the breakpoint you set earlier, well done!

GraphiQL in ActionGraphiQL in Action

Wrap Up

A lot has happened in this first installment, we went from an empty project to a functional sample GraphQL API that serves up a subset of the canonical StarWars schema. So far, this API is wide open to anyone, which isn't what we want over the long term. Future installments will take this solution a little further down the path (links will light up when the content is available!):

Until then, happy coding!

Credits

I want to thank my colleagues at Core BTS for all their assistance in the preparation of this post. Their technical expertise and willingness to share that knowledge is truly inspiring. Specifically, I'd like to thank Andrew Petersen for his technical reviews and feedback.