What is the identity server in ASP.NE core ?

Written By
Published On
Share:

Identity Server

IdentityServer is a .Net/Katana-based framework and hostable component that allows implementing single sign-on and access control for modern web applications and APIs using protocols like OpenID Connect and OAuth2. It supports a wide range of clients like mobile, web, SPAs and desktop applications and is extensible to allow integration in new and existing architectures.1

Features

Terminology

Before we proceed, let’s look at some terminology used by OAuth and Identity Server.

OpenID Connect Provider (OP)555

OpenID Connect Provider is a piece of software that implements OpenID Connect Protocol, (which is built on top of OAuth2) and issues Security Tokens to Clients. Please refer to the link https://openid.net/connect for details about OpenID Connect. There are other terms which refers to OP like Security Token Service (STS), Identity Provider, Authorization Server. Identity Server is an OpenID Connect Provider.

Client

A client is a piece of software that requests tokens from Identity Server (OP) – either for authenticating a user or for accessing a resource. It is also known as Relying Party (RP). To be able to get tokens from OP, client must be registered with OP. A client can be a Web Application, a Single Page Application (SPA), Mobile Application or a Server Process.

Scope

Scopes are identifiers for resources that a client wants to access. This identifiers need to be sent to the OP during an authentication or token request. By default every client is allowed to request tokens for every scope, but we can restrict it. There are 2 types of Scopes.

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.

Resource scopes

Resource scopes identify web APIs (also called resource servers).

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.

Applications Interactions

Modern web applications are not only a single standalone application but instead they are array of applications interacting with one another as shown in the following diagram.

As seen in the diagram:

All of this communication needs to be authenticated to prevent attacks and secure data. We can use OAuth and OpenID connect for this as shown in the following diagram.

As seen in the following diagram, we need to use combination of OpenID connect an OAuth2. Identity Server 3 is an implementation of these two protocols and is highly optimized to solve the typical security problems of modern applications.

Identity Server Flows

OAuth2 and OpenID connect specify how authentication is handled by OP; which things are sent to client and how in terms of flows/grants. Identity Server provides following flows.

1. Authorization Code flow

2. Implicit flow

3. Client Credentials Flow

Sample Application

We’ll build 3 web applications.

Authentication Server Create a new Empty MVC application with ”No Authentication”. Enable SSL for the application from the properties window as shown in the following image.

Install following packages to the application via nuget.

We need to register each of our client applications that wish to use Identity Server for Authentication. We also need to register users and define scopes. In real life scenario this will be saved in a database but for out application we’ll save it in memory.

Clients

We’ll register each of our clients by creating an instance of Client as shown below.
new Client 
{
  ClientId = "TestClient",
  ClientName = "Demo Client Application",
  Enabled = true,
  Flow = Flows.Implicit,
  RequireConsent = false,
  RedirectUris = new List
  {
      "http://localhost:52481/"
  },
  PostLogoutRedirectUris =new List
  {
      "http://localhost:52481/"
  },
  AllowedScopes = new List {
    Constants.StandardScopes.OpenId,
    Constants.StandardScopes.Profile,
    Constants.StandardScopes.Email,
    "GreetingsAPI"
  }
}
The above client will be able to request scopes defined in AllowedScopes property only. Here we’ll use Implicit Flow for simplicity.

Users

We’ll register just one user for now as shown below.
public class Users
{
  public static List Get()
  {
    return new List {
      new InMemoryUser {
        Subject = "ABCD-1234-EFGH-5678",
        Username = "testuser",
        Password = "123456",
        Claims = new List {
          new Claim(Constants.ClaimTypes.PreferredUserName, "TestUser"),
          new Claim(Constants.ClaimTypes.Name, "Test User"),
          new Claim(Constants.ClaimTypes.Email, "test@test.com")
        }
      }
    };
  }
}
The above client will be able to request scopes defined in AllowedScopes property only. Here we’ll use Implicit Flow for simplicity.

Scopes

We’ll use some built in scopes as well as create a new one as shown below that will be used in the Web API we’ll create.
public class Scopes
{
  public static IEnumerable Get()
  {
    return new List {
      StandardScopes.OpenId,
      StandardScopes.Profile,
      StandardScopes.Email,
      StandardScopes.Roles,
      new Scope
      {
        Enabled = true,
        DisplayName = "Greetings API",
        Name = "GreetingsAPI",
        Description = "Access to the Greetings API",
        Type = ScopeType.Resource
      }
    };
  }
}
The above client will be able to request scopes defined in AllowedScopes property only. Here we’ll use Implicit Flow for simplicity.

Configuring Identity Server

We’ll configure IdentityServer in the startup class. Here we’ll provide information about the clients, users, scopes and the signing certificate. In production we should load the signing certificate from the Windows Certificate Store. But here we’ll use test signing certificate provided by Identity Server and we’ll add it in our project itself.

Please follow the instructions provided the link to configure the certificate. Open the Startup class and add the following code to it.

public class Startup
{
  public void Configuration(IAppBuilder app)
  {
    app.Map("/identity", identity =>
     {
       identity.UseIdentityServer(new IdentityServerOptions()
       {
         SiteName = "Authentication Server",
         SigningCertificate = LoadCertificate(),
         Factory = new IdentityServerServiceFactory()
               .UseInMemoryClients(Clients.Get())
               .UseInMemoryScopes(Scopes.Get())
               .UseInMemoryUsers(Users.Get())
       });
     });
  }
  X509Certificate2 LoadCertificate()
  {
    return new X509Certificate2(
      string.Format(@"{0}binidentityServeridsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
  }
}
Here we’ve mapped “/identity” path for Identity Server and loaded configurations from classes we defined earlier.We’ll need to make “runAllManagedModulesForAllRequests” to true in web.config otherwise some of Identity Server’s embedded resources will not be loaded correctly. Add following to web.config.
<system.webserver>
  <modules runallmanagedmodulesforallrequests="true">
</modules></system.webserver>
This completes our Authentication Server setup. Now we’ll create another application that will be one of the clients to this Authentication Server.

Client Application

Create a new MVC application with “No Authentication” and add following packages to it via nuget.

Configure the cookie middleware in StartUp.cs with its default values.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
  AuthenticationType = "Cookies"
});
Now we’ll configure Identity Server as OpenID Connect Provider as shown below.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
  Authority = "https://localhost:44374/identity",
  ClientId = "TestClient",
  RedirectUri = "http://localhost:52481/",
  ResponseType = "id_token",
  Scope = "openid email profile",
  SignInAsAuthenticationType = "Cookies"
});

Here the Authority is the url of our Authentication Server, ClientId is the ClientId we registered with the Authentication Server, ReirectUri is the url of this application, which is also registered with Authentication Server. ResponseType is “id_token” which specifies we need Identity Token in response of successful user authentication. Scope is space separate values of the user claims we require. The “opeid” scope must always be there.

The “OpenIdConnectAuthenticationOptions” has also provides various notifications raised on specific events, which we can use to our requirements. We can store the claims received from the Authentication Server in a cookie using “SecurityTokenValidated” notification as shown below.

Notifications = new OpenIdConnectAuthenticationNotifications
{
  SecurityTokenValidated = n =>
  {
    var claims = n.AuthenticationTicket.Identity;
    var sub = claims.FindFirst(IdentityServer3.Core.Constants.ClaimTypes.Subject);
    var name = claims.FindFirst(IdentityServer3.Core.Constants.ClaimTypes.Name);
    var email = claims.FindFirst(IdentityServer3.Core.Constants.ClaimTypes.Email);
    var username = claims.FindFirst(IdentityServer3.Core.Constants.ClaimTypes.PreferredUserName);
    var nid = new ClaimsIdentity(claims.AuthenticationType);
    nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
    nid.AddClaim(sub);
    nid.AddClaim(name);
    nid.AddClaim(email);
    nid.AddClaim(username);
    n.AuthenticationTicket = new AuthenticationTicket(nid, n.AuthenticationTicket.Properties);
    return Task.FromResult(0);
  }
}
Then we need to have a protected resource; so if a user tries to access that resource; the user will be redirected to Authentication Server if that user is not already authenticated. We’ll create a new Action Method “MyProfile” we’ll protect it by adding “Authorize” attribute. This action method will show claims returned after user is authenticated as shown below.

MyProfile Action Method

[Authorize]
public ActionResult MyProfile()
{
  var claims = (User as ClaimsPrincipal).Claims;
  var idClaim = claims.FirstOrDefault(c => c.Type == IdentityServer3.Core.Constants.ClaimTypes.Subject);
  var nameClaim = claims.FirstOrDefault(c => c.Type == IdentityServer3.Core.Constants.ClaimTypes.Name);
  var emailClaim = claims.FirstOrDefault(c => c.Type == IdentityServer3.Core.Constants.ClaimTypes.Email);
  var usernameClaim = claims.FirstOrDefault(c => c.Type == IdentityServer3.Core.Constants.ClaimTypes.PreferredUserName);
  var model = new ProfileModel
  {
    Id = idClaim.Value,
    Name = nameClaim.Value,
    Email = emailClaim.Value,
    Username = usernameClaim.Value
  };
  return View(model);
}

MyProfile View

@model ClientApplication.Models.ProfileModel
@{
    ViewBag.Title = "Profile";
}
<h2>Profile</h2>
<div class="col-md-12">
    <div class="form-group">
        <label class="control-label">Id</label>
        <p class="form-control-static">@Model.Id</p>
    </div>
    <div class="form-group">
        <label class="control-label">Name</label>
        <p class="form-control-static">@Model.Name</p>
    </div>
    <div class="form-group">
        <label class="control-label">Email</label>
        <p class="form-control-static">@Model.Email</p>
    </div>
    <div class="form-group">
        <label class="control-label">Username</label>
        <p class="form-control-static">@Model.Username</p>
    </div>
</div>

Resource Server

Now we’ll create a web api that will be accessed by our client application. This API will be protected using our authentication server. Create a new Web API project and add following nuget packages.

We’ll just add a simple controller and an action method that returns only “Hello World” and we’ll add authorize attribute on that method.
public class GreetingsController : ApiController
{
  [Authorize]
  [HttpGet]
  public IHttpActionResult Get()
  {
    return Ok("Hello World");
  }
}
We’ll configure the API to use our Authentication Server as OpenID Connect Provider(OP) as shown below.
public class Startup
{
  public void Configuration(IAppBuilder app)
  {
    app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
    {
      Authority = "https://localhost:44374/identity",
      RequiredScopes = new[] { "GreetingsAPI" }
    });
  }
}

Here we have specified “RequiredScopes” as “GreetingsAPI”; which is our custom scope that we have registered with OP. So if any client application that wish to access this API must provide a Bearer Authentication Token that contains “GreetingsAPI” scope. So to be able to access this API our client needs to send request to our OP to get Access Token. We’ll use Client Credential flow and “TokenClient” class to get Access Token.

Create a new Action Method in “HomeController” of Client Application. This method will first get the access token and then using this access token it will call the API created above.

private string APIUrl = "http://localhost:58340/api";
private string AuthServerUrl = "https://localhost:44374";
public async Task CallAPIWithToken()
{
  var tokenUrl = $"{AuthServerUrl}/identity/connect/token";
  var tokenClient = new TokenClient(tokenUrl, "GreetingAPIClient", "secret");
  var tokenResponse = await tokenClient.RequestClientCredentialsAsync("GreetingsAPI");
  using (var client = new HttpClient())
  {
    client.SetBearerToken(tokenResponse.AccessToken);
    var result = await client.GetStringAsync(APIUrl + "/Greetings");
    return View((object)result);
  }
}

Full source code for identity server demo using in memory client and users is available on github link.

Get the expert advice to grow your business digitally

    ×

    Table Of Content