Welcome to IdentityServer4

IdentityServer4 is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core.

It enables the following features in your applications:

Authentication as a Service

Centralized login logic and workflow for all of your applications (web, native, mobile, services).

Single Sign-on / Sign-out

Single sign-on (and out) over multiple application types.

Access Control for APIs

Issue access tokens for APIs for various types of clients, e.g. server to server, web applications, SPAs and native/mobile apps.

Federation Gateway

Support for external identity providers like Azure Active Directoy, Google, Facebook etc. This shields your applicatios from the details of how to connect to these external providers.

Focus on Customization

The most important part - many aspect of IdentityServer can be customized to fit your needs. Since IdentityServer is a framework and not a boxed product or a SaaS, you can write code to adapt the system the way it makes sense for your scenarios.

The big Picture

Most modern applications look more or less like this:

_images/appArch.png

The typical interactions are:

  • Browsers communicate with web applications
  • Web applications communicate with web APIs (sometimes on their own, sometimes on behalf of a user)
  • Browser-based applications communicate with web APIs
  • Native applications communicate with web APIs
  • Server-based applications communicate with web APIs
  • Web APIs communicate with web APIs (sometimes on their own, sometimes on behalf of a user)

Typically each and every layer (front-end, middle-tier and back-end) has to protect resources and implement authentication and/or authorization – and quite typically against the same user store.

This is why we don’t implement these fundamental security functions in the business applications/endpoints themselves, but rather outsource that critical functionality to a service - the security token service.

This leads to the following security architecture and usage of protocols:

_images/protocols.png

This divides the security concerns into two parts.

Authentication

Authentication is needed when an application needs to know about the identity of the current user. Typically these applications manage data on behalf of that user and need to make sure that this user can only access the data he is allowed to. The most common example for that is (classic) web applications – but native and JS-based applications also have need for authentication.

The most common authentication protocols are SAML2p, WS-Federation and OpenID Connect – SAML2p being the most popular and the most widely deployed.

OpenID Connect is the newest of the three, but is generally considered to be the future because it has the most potential for modern applications. It was built for mobile application scenarios right from the start and is designed to be API friendly.

API Access

Applications have two fundamental ways with which they communicate with APIs – using the application identity, or delegating the user’s identity. Sometimes both ways need to be combined.

OAuth2 is a protocol that allows applications to request access tokens from a security token service and use them to communicate with APIs. This reduces complexity on both the client applications as well as the APIs since authentication and authorization can be centralized.

OpenID Connect and OAuth2 – better together

OpenID Connect and OAuth2 are very similar – in fact OpenID Connect is an extension on top of OAuth2. This means that you can combine the two fundamental security concerns – authentication and API access into a single protocol – and often a single round trip to the security token service.

This is why we believe that the combination of OpenID Connect and OAuth2 is the best approach to secure modern applications for the foreseeable future. IdentityServer3 is an implementation of these two protocols and is highly optimized to solve the typical security problems of today’s mobile, native and web applications.

Terminology

The specs, documentation and object model use a certain terminology that you should be aware of.

_images/terminology.png

OpenID Connect Provider (OP)

IdentityServer is an OpenID Connect provider - it implements the OpenID Connect protocol (and OAuth2 as well).

Different literature uses different terms for the same role - you probably also find security token service, identity provider, authorization server, IP-STS and more.

But they are in a nutshell all the same: a piece of software that issues security tokens to clients.

IdentityServer has a number of jobs and features - including:

  • authenticate users using a local account store or via an external identity provider
  • provide session management and single sign-on
  • manage and authenticate clients
  • issue identity and access tokens to clients
  • validate tokens

Client

A client is a piece of software that requests tokens from IdentityServer - either for authenticating a user or for accessing a resource (also often called a relying party or RP). A client must be registered with the OP.

Examples for clients are web applications, native mobile or desktop applications, SPAs, server processes etc.

User

A user is a human that is using a registered client to access his or her data.

Scope

Scopes are identifiers for resources that a client wants to access. This identifier is sent to the OP during an authentication or token request.

By default every client is allowed to request tokens for every scope, but you can restrict that.

They come in two flavours.

Identity scopes Requesting identity information (aka claims) about a user, e.g. his name or email address is modeled as a scope in OpenID Connect.

There is e.g. a scope called profile that includes first name, last name, preferred username, gender, profile picture and more. You can read about the standard scopes here and you can create your own scopes in IdentityServer to model your own requirements.

Resource scopes Resource scopes identify web APIs (also called resource servers) - you could have e.g. a scope named calendar that represents your calendar API.

Authentication/Token Request

Clients request tokens from the OP. Depending on the scopes requested, the OP will return an identity token, an access token, or both.

Identity Token

An identity token represents the outcome of an authentication process. It contains at a bare minimum an identifier for the user (called the sub aka subject claim). It can contain additional information about the user and details on how the user authenticated at the OP.

Access Token

An access token allows access to a resource. Clients request access tokens and forward them to an API. Access tokens contain information about the client and the user (if present). APIs use that information to authorize access to their data.

Supported Specifications

IdentityServer implements the following specifications:

  • OpenID Connect Core 1.0 (spec)
  • OpenID Connect Discovery 1.0 (spec)
  • OpenID Connect Session Management 1.0 - draft 22 (spec)
  • OpenID Connect HTTP-based Logout 1.0 - draft 03 (spec)
  • OAuth 2.0 (RFC 6749)
  • OAuth 2.0 Bearer Token Usage (RFC 6750)
  • OAuth 2.0 Multiple Response Types (spec)
  • OAuth 2.0 Form Post Response Mode (spec)
  • OAuth 2.0 Token Revocation (RFC 7009)
  • OAuth 2.0 Token Introspection (RFC 7662)

Packaging and Builds

IdentityServer consists of a number of nuget packages.

IdentityServer4

nuget | github

Contains the core IdentityServer object model, services and middleware. Only contains support for in-memory configuration and user stores - but you can plug-in support for other stores via the configuration. This is what the other repos and packages are about.

Access token validation middleware

nuget | github

ASP.NET Core middleware for validating tokens in APIs. Provides an easy way to validate access tokens (both JWT and reference) and enforce scope requirements.

Dev builds

In addition we publish dev/interim builds to MyGet. Add the following feed to your Visual Studio if you want to give them a try:

https://www.myget.org/F/identity/

Defining Scopes

The first thing you typically define in your system are the resources that you want to protect. That could be identity information of your users like profile data or email addresses or access to APIs.

Note

At runtime, scopes are retrieved via an implementation of the IScopeStore. This allows loading them from arbitrary data sources like config files or databases. For this document we gonna use the in-memory version of the scope store. You can wire up the in-memory store in ConfigureServices via the AddInMemoryScopes extensions method.

Defining the minimal scope for OpenID Connect

OpenID Connect requires a scope with a name of openid. Since this scope is defined in the OIDC specification, we have built-in support for it via the StandardScopes class.

Alls our samples define a class called Scopes with a method called Get. In this method you simply return a list of scopes you want to support in your identityserver. This list will be later used to configure the identityserver service:

public class Scopes
{
    public static IEnumerable<Scope> Get()
    {
        return new List<Scope>
        {
            StandardScopes.OpenId
        };
    }
}

The StandardScopes class supports all scopes defined in the specification (openid, email, profile, address and offline_access). If you want to support them all, you can add them to your list of supported scopes:

public class Scopes
{
    public static IEnumerable<Scope> Get()
    {
        return new List<Scope>
        {
            StandardScopes.OpenId,
            StandardScopes.Profile,
            StandardScopes.Email,
            StandardScopes.Address,
            StandardScopes.OfflineAccess
        };
    }
}

Defining custom identity scopes

You can also define custom identity scopes. Create a new Scope class, give it a name and a display name and define which user claims should be included in the identity token when this scope gets requested:

new Scope
{
    Name = "tenant.info",
    DisplayName = "Tenant Information",
    Type = ScopeType.Identity,

    Claims = new List<ScopeClaim>
    {
        new ScopeClaim("tenantid"),
        new ScopeClaim("subscriptionid")
    }
}

Add that scope to your list of supported scopes.

Defining scopes for APIs

To get access tokens for APIs, you also need to register them as a scope. This time the scope type is of type Resource:

new Scope
{
    Name = "api1",
    DisplayName = "API #1",
    Description = "API 1",
    Type = ScopeType.Resource
}

If you don’t define any scope claims, the access token will contain the subject ID of the user (if present), the client ID and the scope name.

You can also add additional user claims to the token by defining scope claims as shown above.

Defining Clients

Clients represent applications that can request tokens from your identityserver.

The details vary, but you typically define the following common settings for a client:

  • a unique client ID
  • a secret if needed
  • the allowed interactions with the token service (called a grant type)
  • a network location where identity and/or access token gets sent to (called a redirect URI)
  • a list of scopes (aka resources) the client is allowed to access

Note

At runtime, clients are retrieved via an implementation of the IClientStore. This allows loading them from arbitrary data sources like config files or databases. For this document we gonna use the in-memory version of the client store. You can wire up the in-memory store in ConfigureServices via the AddInMemoryClients extensions method.

Defining a client for server to server communication

In this scenario no interactive user is present - a service (aka client) wants to communicate with an API (aka scope):

public class Clients
{
    public static IEnumerable<Client> Get()
    {
        return new List<Client>
        {
            new Client
            {
                ClientId = "service.client",
                ClientSecrets = new List<Secret>
                {
                    new Secret("secret".Sha256())
                },

                AllowedGrantTypes = GrantTypes.ClientCredentials,

                AllowedScopes = new List<string>
                {
                    "api1", "api2"
                }
            };
        }
    }
}

Defining browser-based JavaScript client (e.g. SPA) for user authentication and delegated access and API

This client uses the so called implicit flow to request an identity and access token from JavaScript:

var jsClient = new Client
{
    ClientId = "js",
    ClientName = "JavaScript Client",
    ClientUri = "http://identityserver.io",

    AllowedGrantTypes = GrantTypes.Implicit,
    AllowAccessTokensViaBrowser = true,

    RedirectUris = new List<string>
    {
        "http://localhost:7017/index.html",
    },
    PostLogoutRedirectUris = new List<string>
    {
        "http://localhost:7017/index.html",
    },
    AllowedCorsOrigins = new List<string>
    {
        "http://localhost:7017"
    },

    AllowedScopes = new List<string>
    {
        StandardScopes.OpenId.Name,
        StandardScopes.Profile.Name,
        StandardScopes.Email.Name,
        "api1", "api2"
    },
};

Defining a server-side web application (e.g. MVC) for use authentication and delegated API access

Interactive server side (or native desktop/mobile) applications use the hybrid flow. This flow gives you the best security because the access tokens are transmitted via back-channel calls only (and gives you access to refresh tokens):

var mvcClient = new Client
{
    ClientId = "mvc",
    ClientName = "MVC Client",
    ClientSecrets = new List<Secret>
    {
        new Secret("secret".Sha256())
    },
    ClientUri = "http://identityserver.io",

    AllowedGrantTypes = GrantTypes.Hybrid,

    RedirectUris = new List<string>
    {
        "http://localhost:21402/signin-oidc"
    },
    PostLogoutRedirectUris = new List<string>
    {
        "http://localhost:21402/"
    },
    LogoutUri = "http://localhost:21402/signout-oidc",

    AllowedScopes = new List<string>
    {
        StandardScopes.OpenId.Name,
        StandardScopes.Profile.Name,
        StandardScopes.OfflineAccess.Name,

        "api1", "api2",
    },
};

Scope

The Scope class models a resource in your system.

  • Enabled

    Indicates if scope is enabled and can be requested. Defaults to true.

  • Name

    The unique name of the scope. This is the value a client will use to request the scope.

  • DisplayName

    Display name for consent screen.

  • Description
    • Description for the consent screen.
  • Required

    Specifies whether the user can de-select the scope on the consent screen. Defaults to false.

  • ScopeSecrets

    Adds a secret to scope for accessing the the introspection endpoint - see also [here](secrets.html).

  • AllowUnrestrictedIntrospection

    Allows this scope to see all other scopes in the access token when using the introspection endpoint

  • Emphasize

    Specifies whether the consent screen will emphasize this scope. Use this setting for sensitive or important scopes. Defaults to false.

  • Type

    Either Identity (OpenID Connect related) or Resource (OAuth2 resources). Defaults to Resource.

  • Claims

    List of user claims that should be included in the identity (identity scope) or access token (resource scope).

  • IncludeAllClaimsForUser

    If enabled, all claims for the user will be included in the token. Defaults to false.

  • ClaimsRule

    Rule for determining which claims should be included in the token (this is implementation specific)

  • ShowInDiscoveryDocument

    Specifies whether this scope is shown in the discovery document. Defaults to true.

Scope can also specify claims that go into the corresponding token - the ScopeClaim class has the following properties:

  • Name

    Name of the claim

  • Description

    Description of the claim

  • AlwaysIncludeInIdToken

    Specifies whether this claim should always be present in the identity token (even if an access token has been requested as well). Applies to identity scopes only. Defaults to false.

Client

The Client class models an OpenID Connect or OAuth2 client - e.g. a native application, a web application or a JS-based application.

  • Enabled

    Specifies if client is enabled. Defaults to true.

  • ClientId

    Unique ID of the client

  • ClientSecrets

    List of client secrets - credentials to access the token endpoint.

  • ClientName

    Client display name (used for logging and consent screen)

  • ClientUri

    URI to further information about client (used on consent screen)

  • LogoUri

    URI to client logo (used on consent screen)

  • RequireConsent

    Specifies whether a consent screen is required. Defaults to true.

  • AllowRememberConsent

    Specifies whether user can choose to store consent decisions. Defaults to true.

  • AllowedGrantTypes

    Specifies the grant types the client is allowed to use. Use the GrantTypes class for common combinations.

  • RedirectUris

    Specifies the allowed URIs to return tokens or authorization codes to

  • PostLogoutRedirectUris

    Specifies allowed URIs to redirect to after logout

  • LogoutUri

    Specifies logout URI at client for HTTP based logout

  • LogoutSessionRequired

    Specifies if the user’s session id should be sent to the LogoutUri. Defaults to true.

  • RequireSignOutPrompt

    Specifies if the client will always show a confirmation page for sign-out. Defaults to false.

  • AllowedScopes

    By default a client has no access to any scopes - either specify the scopes explicitly here (recommended) - or set AllowAccessToAllScopes to true.

  • AllowAccessTokensViaBrowser

    Specifies whether this client is allowed to request access tokens via the browser. This is useful to harden flows that allow multiple response types (e.g. by disallowing a hybrid flow client that is supposed to use code id_token to add the token response type and thus leaking the token to the browser.

  • IdentityTokenLifetime

    Lifetime to identity token in seconds (defaults to 300 seconds / 5 minutes)

  • AccessTokenLifetime

    Lifetime of access token in seconds (defaults to 3600 seconds / 1 hour)

  • AuthorizationCodeLifetime

    Lifetime of authorization code in seconds (defaults to 300 seconds / 5 minutes)

  • AbsoluteRefreshTokenLifetime

    Maximum lifetime of a refresh token in seconds. Defaults to 2592000 seconds / 30 days

  • SlidingRefreshTokenLifetime

    Sliding lifetime of a refresh token in seconds. Defaults to 1296000 seconds / 15 days

  • RefreshTokenUsage
    • ReUse: the refresh token handle will stay the same when refreshing tokens
    • OneTime: the refresh token handle will be updated when refreshing tokens
  • RefreshTokenExpiration
    • Absolute: the refresh token will expire on a fixed point in time (specified by the AbsoluteRefreshTokenLifetime)
    • Sliding: when refreshing the token, the lifetime of the refresh token will be renewed (by the amount specified in SlidingRefreshTokenLifetime). The lifetime will not exceed AbsoluteRefreshTokenLifetime.
  • UpdateAccessTokenClaimsOnRefresh

    Gets or sets a value indicating whether the access token (and its claims) should be updated on a refresh token request.

  • AccessTokenType

    Specifies whether the access token is a reference token or a self contained JWT token (defaults to Jwt).

  • EnableLocalLogin

    Specifies if this client can use local accounts, or external IdPs only. Defaults to true.

  • IdentityProviderRestrictions

    Specifies which external IdPs can be used with this client (if list is empty all IdPs are allowed). Defaults to empty.

  • IncludeJwtId

    Specifies whether JWT access tokens should have an embedded unique ID (via the jti claim).

  • AllowedCorsOrigins

    If specified, will be used by the default CORS policy service implementations (In-Memory and EF) to build a CORS policy for JavaScript clients.

  • Claims

    Allows settings claims for the client (will be included in the access token).

  • AlwaysSendClientClaims

    If set, the client claims will be sent for every flow. If not, only for client credentials flow (default is false)

  • PrefixClientClaims

    If set, all client claims will be prefixed with client_ to make sure they don’t accidentally collide with user claims. Default is true.