A common question on Stack Overflow is about the different resource types in Dunde IdentityServer. My answer to this question on Stack Overflow is one of my most upvoted answers on Stack Overflow. With this blog post, I want to go further about what these resources are used for and how they affect your IdentityServer setup.

IdentityServer – IdentityResource vs. ApiResource vs. ApiScope

A common question on Stack Overflow is about the different resource types in Dunde IdentityServer. My answer to this question on Stack Overflow is one of my most upvoted answers on Stack Overflow.

With this blog post, I want to go further about what these resources are used for and how they affect your IdentityServer setup.

Back to basics

In IdentityServer, there are three types of resources

  • Identity Resources
  • API Scopes
  • API Resources

How are these related, and what do they control?

Before we can answer this, we need to take one step back and talk about scopes.

Scopes

When the client application authenticates, it will ask Identity Server for a set of scopes, as the picture below shows:

The list of scopes represents a mixed list of what the client wants to get back. So far, so good!

As the picture shows above, we can divide the list of scopes into two categories:

  • Identity scopes
    Scopes that are all about what information the client wants to know about the user
  • Access scopes
    Scopes that represent what the client wants to have access to.

These two categories control what goes into the id and access token.

Identity Scopes

The Identity scopes control what goes into the ID token (or is available from the UserInfo endpoint). In IdentityServer, we define these scopes using Identity Resources.

The three identity scopes in our example above can, in code, be defined as follows:

				
					_identityResources = new List<IdentityResource>()
{
    new IdentityResources.OpenId(),
    new IdentityResources.Email(),
    employeeInfoScope
};

var employeeInfoScope = new IdentityResource()
{
    Name = "employee_info",
    DisplayName = "Employee information",
    Description = "Employee information including seniority and status...",
};



				
			

In the code above, we define that clients can ask for the standardized openid and email scope and a custom scope named employee_info.

The employee_info scope, as defined above, is pretty useless. We also typically want to add what claims this scope represents.

The following picture shows how the identity resources are connected to a set of claims:

The list of requested claims represents what claims will end up in the ID token. Don’t forget that only the claims found in the user database will be included.

In code, this means that we will add a list of user claims to our custom scope:

				
					var employeeInfoScope = new IdentityResource()
{
    Name = "employee_info",
    DisplayName = "Employee information",
    Description = "Employee information including seniority and status...",
    UserClaims = new List<string>
    {
        "employment_start",
        "seniority",
        "contractor"
    }
};


				
			

Access scopes

The access scopes control what APIs and services the client application wants to access and what should go into the access token. In IdentityServer, we define these scopes using ApiScopes.

In code, we can define them as shown in this example:

				
					_apiScopes = new List<ApiScope>()
{
    new ApiScope()
    {
        Name = "payment",
        DisplayName = "Payments access",
        Description = "Access to the payment related services.",
        UserClaims = new List<string>
        {
            //These claims will be added to the access token, not the ID-token!
            "bonuslevel",
            "sendpayments"
        }
    },

    new ApiScope()
    {
        Name = "invoice",
        DisplayName = "Invoices access",
        UserClaims = new List<string>
        {
            "approveinvoices",
            "sendinvoices"
        }
    }
};


				
			

The image below shows how the API Scopes and user claims are related based on the code above:

The requested claims will be added to the access token, not the id token. These claims are useful when, for example, services and APIs need to authorize received access tokens.

Also, it is important to note that the requested scopes (Identity resources and API scopes) are what the user will give consent to during authentication:

API Resources

Just defining Identity resources and Api scopes is a good start. But we can improve our setup further by introducing API Resources.

One problem with the current setup is that the aud claim inside the access token contains a generic value:

				
					{
  "iss": "https://identity.secure.nu:6001",
  "aud": "https://identity.secure.nu:6001/resources",
  "scope": [
    "openid",
    "email",
    "employee_info",
    "payment",
    "invoice"
  ],

				
			

If we set the EmitStaticAudienceClaim setting to false in IdentityServer:

				
					builder.Services.AddIdentityServer(options =>
{
    options.EmitStaticAudienceClaim = false;
})


				
			

Then the aud claim is not even included:

				
					{
  "iss": "https://identity.secure.nu:6001",
  "scope": [
    "openid",
    "email",
    "employee_info",
    "payment",
    "invoice"
  ],


				
			

The access tokens above are a bit too generic, and we often want a more “targeted” access token. That allows the intended service receiving the token to verify that the service is the intended target for the token.

By introducing API Resources, we can control what goes into the audience (aud) claim and further improve our setup. For example, we can define two API resources as follows:

				
					var paymentApi = new ApiResource()
{
    Name = "paymentapi",   
    Scopes = new List<string> { "payment" },
    UserClaims =
    {
        //Custom user claims that should be provided when requesting access to this API.
        //These claims will be added to the access token, not the ID token!
        "employee",
        "contractor"
    }
};

var invoiceApi = new ApiResource()
{
    Name = "invoiceapi", 
    Scopes = new List<string> { "invoice" },
};

_apiResources = new List<ApiResource>()
{
    paymentApi,
    invoiceApi,
};


				
			

The image below shows the relationship between the Api Scopes and Api Resources as defined in the code above:

When adding these two API resources, the access token will now contain the following:

				
					{
  "iss": "https://identity.secure.nu:6001",
  "aud": [
    "paymentapi",
    "invoiceapi"
  ],
  "scope": [
    "openid",
    "email",
    "employee_info",
    "payment",
    "invoice"
  ],
  "contractor": "no",
  "employee": "yes",
  "approveinvoices": "no",
  "sendinvoices": "yes",
  "sendpayments": "yes",
  "bonuslevel": "42",


				
			

The API can verify if it is the intended target by checking the aud claim inside the received access tokens. In addition, the use claims defined in the API Scopes and API Resources are also included in the access token.

As described in the Duende IdentityServer documentation, adding API Resources gives you these additional benefits:

  • support for the JWT aud claim. The value(s) of the audience claim will be the name of the API resource(s)
  • support for adding common user claims across all contained scopes
  • support for introspection by assigning an API secret to the resource
  • support for configuring the access token signing algorithm for the resource

Feedback

If you have any feedback or questions on this blog post or would like to get in touch, you can find my contact details here

About the author

Hi, I’m Tore! I have been fascinated by computers since I unpacked my first Commodore VIC-20. Today, I enjoy providing freelance development and developer training services, focusing on ASP.NET Core, IdentityServer, OpenID Connect, Architecture, and Web Security. You can connect with me on LinkedIn and Twitter, and you can find out more about me and my services here, as well as my classes for developers, including my course, ‘Introduction to IdentityServer and OpenID-Connect’

Share This Story
Share on linkedin
Share on twitter
Share on facebook
Share on pinterest
Share on email

About me

My name is Tore Nestenius and I’m a trainer and senior software developer focusing on Architecture, Security and Identity, .NET, C#, Backend, and Cloud, among other things.

Do You Want Tore To Be Your Mentor?

Categories