Authorization Code Flow - C# .NET Core

GitHub Live Demo


We use OAuth 2.0 to secure access to a user's SKY API data. In this tutorial we obtain user authorization using the Authorization Code Flow. From the user's perspective, the user authenticates as a Blackbaud user with the normal credentials for Blackbaud NXT and then authorizes (or denies) your application. To accomplish this, your application obtains an authorization code from the Blackbaud Authorization Service. The authorization code is then exchanged for an access token that signs requests to the SKY API on behalf of the user. The exchange involves your registered application's Application secret. For security reasons, the exchange is done through direct server-to-server communication. For this reason, we use ASP.NET Core for the server-side platform and C#.

In this tutorial, we will accomplish the following tasks:

  • Ensure that you signed up for a developer account and obtained your subscription to an API product.
  • Register an application with SKY API.
  • Obtain authorization to access user data for a specific tenant.
  • Retrieve data from a SKY API endpoint.

For this tutorial, we strip down the user interface to highlight the Authorization Code Flow. Our Barkbaud code sample provides a rich user interface using SKY UX.


  1. Since we are using the Authorization Code Flow, we need to use a server-side software platform, such as .NET Core. You will need to Download and install .NET Core
  2. Familiarity using a command line interface (CLI) such as Terminal or Windows Command Prompt.
  3. Sign up for a GitHub account, if you don't already have one. The source code for this tutorial is stored in GitHub repository.
  4. Install Git and have the ability to clone or fork a repo.
  5. A reliable Internet connection.

Step 1 — Get Your Keys

If you have not already done so, complete the Getting Started guide. The tutorial guides you through signing up for a Blackbaud developer account and requesting a subscription to an API product. After you are approved, your subscription contains a Primary key and Secondary key. You can use either key as the subscription key value for the Bb-Api-Subscription-Key request header in calls to the API.

Developer Sandbox Tenant

After your subscription is approved, your developer account can access the Developer Sandbox tenant that represents a sample database. With this particular tenant, keep in mind that you share this sandbox with other developers. You can access the Developer Sandbox tenant and learn the various endpoints through the interactive SKY API Console within the API Reference.

Step 2 — Register Your App

To call the SKY API, first register your application to obtain its unique set of credentials, which your users will use to enable your app to access their data

  1. From My Applications, click Register app.

  2. Enter the name, description, and logo of your application, as well as your organization's name. This information appears for users when they enable access to your application during the authorization process or in their product.

  3. In the Application website URL field, enter where users can learn more about your application online.

  4. Specify the URIs to use to redirect users back to your application during the authorization process. Note: The URIs must be absolute and use HTTPS. However, we do support http://localhost:port or for local development.

    Important!   When your application requests authorization to access a Blackbaud user's data, it includes a redirect_uri parameter in its query string. To authorize your application, this value must match exactly against one of the URIs you provide, including any trailing slashes. For more information, see common authorization issues.

  5. Click Save.

After you register an app, note the ID and secret that appear under Application Credentials. These credentials are unique to your application, and verify its identity during the authorization process.

ID -
Your application's unique identifier. Your users will need this ID to enable your application to access their Blackbaud data. You can't modify this ID; if you need to change it for any reason, delete the application and re-register it.

Secret -
The key your application provides when it requests an access token to call the SKY API during the authorization process. This value is sensitive, so don't share it with anyone else! To display the secret, click Show.

Very Important!   Keep the application secret private and safe! If the secret is compromised, regenerate it. Blackbaud reserves the right to remove or deactivate your application to protect customer data.

Step 3 — Grab the Source Code

The sky-api-auth-tutorial-c-sharp repo on GitHub provides a starter project to work through the Authorization Code Flow.

  • Use a command prompt to clone the sky-api-auth-tutorial-c-sharp repo which creates a working directory by the same name that contains the code for the tutorial:

    $ git clone

Step 4 — Prepare Your Environment

Let's explore some application settings files and prep your environment variables.

  • Open the sky-api-auth-tutorial-c-sharp working directory.
  • Open the appsettings.json which details generic properties to be used by the application. Take note of the following SKY API URI property values:

    AuthBaseUri URI to the Authorization Service.
    SkyApiBaseUri URI to the SKY API Endpoints.
  • For local development, duplicate the file appsettings.json-sample, renaming it to appsettings.Development.json. The appsettings.Development.json file contains your registered application's environment variables. Note that private properties, such as your Application ID and Application secret, are stored in this file. For security, the appsettings.Development.json is excluded from being syncronized with GitHub via the file .gitignore.

    At run time, the application will merge these properties into the available environment variables. In .NET Core, the Startup class, Startup.cs provides the entry point for an application, and is required for all applications. This class configures the application and sets up the required middleware.

      "AppSettings": {
          "AuthClientId": "",
          "AuthClientSecret": "",
          "AuthRedirectUri": "http://localhost:5000/auth/callback",
          "AuthSubscriptionKey": ""
  • Using the values from your registered application and the subscription key from your Blackbaud Developer Profile, update appsettings.Development.json with the following values. All values are required.:

    AuthClientId Your registered application's Application ID.
    AuthClientSecret Your registered application's Application secret.
    AuthRedirectUri One of your registered application's Redirect URIs.
    For this tutorial, we will use http://localhost:5000/auth/callback.
    AuthSubscriptionKey Your Blackbaud Developer Subscription Key.
    Use either the Primary key or Secondary key, visible on your Blackbaud Developer Profile.
  • Save the file.

Step 5 — Run the Application

  • Using Command Prompt/Terminal/bash prompt, ensure you are in the working directory.
  • Open and review the project.json file. This file details the application’s dependencies. These NuGet modules (project dependencies) are installed when issuing the dotnet restore command within Command Prompt/Terminal/bash prompt.
  • On a Mac, issue the following commands to restore the packages specified in the project.json file and run the actual sample:

    $  dotnet restore
    $  export ASPNETCORE_ENVIRONMENT=Development && dotnet run

    On a PC, type:

    $  dotnet restore
    $  set ASPNETCORE_ENVIRONMENT=Development && dotnet run
  • Visit http://localhost:5000/ to view your locally running application.

Application Starting Point

  • IIS is used as the webserver. An ASP.NET Core Module configured in project.json is used to list dependencies and configure IIS to launch and host your application.
  • Open the Program.cs file. The Main() method is the starting point of our application. It is responsible for initializing the application. We use WebHostBuilder to listen on a particular IP address and port: http://localhost:5000/. As of RC2 an ASP.NET Core application is a .NET Core Console application that calls into ASP.NET specific libraries. This code runs on the server-side and is not visible to the application user. Running the code server-side helps to protect your Application secret.
  • using System.IO;
    using Microsoft.AspNetCore.Hosting;
    namespace Blackbaud.AuthCodeFlowTutorial
        public class Program
            public static void Main(string[] args)
                var host = new WebHostBuilder()
  • Open your browser to http://localhost:5000/ to request the Home page. When the front page loads, the AngularJS on the Home page (/Views/Shared/_Layout.cshtml) makes a request to the /auth/authenticated endpoint:

    (function (angular) {
      'use strict';
      angular.module('AuthCodeFlowTutorial', [])
        .controller('ConstituentCtrl', function ($scope, $http) {
          // Check user access token.
          $http.get('/auth/authenticated').then(function (res) {
            $scope.isAuthenticated =;
            if ($scope.isAuthenticated === false) {
              $scope.isReady = true;

    The call to the endpoint routes to the corresponding controller (/Controllers/AuthenticationController.cs) and returns a Boolean representing the current user's authentication status. If the application has not yet been authorized by the end user, then IsAuthenticated() will return false.

    public ActionResult Authenticated()
        return Json(new {
            authenticated = _authService.IsAuthenticated()

Displaying the Log in button

  • If the user is not authenticated, a Log in button is displayed.

    Note: The browser may display a warning that the connection is not private. For this tutorial, you can ignore this message. To proceed, click Show advanced and then click Proceed to localhost (unsafe).

  • Open the Views/Shared/_Layout.cshtml file. Notice that the body tag includes an attribute named ng-app. The front-end of our application uses AngularJS to interact with our Web server routes. The div.container element includes an attribute named ng-controller which references an AngularJS controller to handle the model data.

    <body ng-app="AuthCodeFlowTutorial">
      <div class="container" ng-controller="ConstituentCtrl" ng-cloak>
  • When the Home page first loads the app will not have received authorization from the user to access their SKY API data. As a result the isAuthenticated scope variable will be false and the Home page's Angular HTML template displays the Log in button:

    <div ng-if="!isAuthenticated">
        <a href="/auth/login" class="btn btn-primary">Log in</a>

Obtain an Access Token

  • Open /Controllers/AuthenticationController.cs.
  • When the user clicks the Log in button, a call is made to LogIn() which redirects the browser to SKY API’s authorization endpoint to start the authentication and authorization process. The user must authenticate with their Blackbaud credentials (if they are not already signed in) and authorize your application to access their SKY API data.

    public ActionResult LogIn()
        Uri address = _authService.GetAuthorizationUri();
        return Redirect(address.ToString());
  • Once authorized, SKY API redirects the user back to the /auth/callback URI with an authorization code. Once an authorization code has been obtained, it is exchanged the code for an access token. The app is then redirected back to the Home page.

    public ActionResult Callback()
        string code = Request.Query["code"];
        return Redirect("/");

Retrieve Constituent Data

  • Open the Home page (Views/Shared/_Layout.cshtml).
  • AngularJS again makes the request to auth/authenticated, which now returns true. Since the user is authorized, AngularJS then makes a request the application’s constituent API endpoint /api/constituents/280 to retrieve a constituent record:

    angular.module('AuthCodeFlowTutorial', [])
        .controller('ConstituentCtrl', function ($scope, $http) {
            // Check user access token.
            $http.get('/auth/authenticated').then(function (res) {
                $scope.isAuthenticated =;
                if ($scope.isAuthenticated === false) {
                    $scope.isReady = true;
                // Access token is valid. Fetch constituent record.
                $http.get('/api/constituents/280').then(function (res) {
                    $scope.constituent =;
                    $scope.isReady = true;
  • The data is returned as JSON to the Home page where the model's data is projected through the view of the HTML template:

    <div ng-if="isAuthenticated">
      <h3>Constituent: </h3>
        See <a href="/api/entity-reference/constituent">Constituent</a>
        within the SKY API entity reference for a full listing of properties.
      <p ng-if="::constituent.error" ng-bind="::constituent.error" class="alert alert-danger"></p>
      <div ng-if="" class="table-responsive">
        <table class="table table-striped table-hover">
              <td>{{ }}</td>
              <td>{{ constituent.type }}</td>
              <td>{{ constituent.lookup_id }}</td>
              <td>{{ constituent.first }}</td>
              <td>{{ constituent.last }}</td>
    GET Constituent
  • Once the constituent information is retrieved and added to the front page, Log Out and Refresh Access Token buttons are displayed.
  • Open /Controllers/AuthenticationController.cs
  • If the user clicks Log Out, they are redirected to /auth/logout which destroys the access/refresh token stored in the browser’s session.
  • If the user clicks Refresh Access Token, AngularJS makes a request to /auth/refresh-token, which asks SKY API to return a refreshed access token, which is then stored in the browser’s session.

That's it!

  • Be sure to take a look at our other code samples.
  • Check out the README where you can view a live demo of the application hosted on Microsoft Azure.
  • The README also contains instructions for deploying to Azure App Services rather than your local development environment.
  • You can create an issue to report a bug or request a feature for this code sample. For all other feature requests, see ideas.