Claims-Based Authorization and Authentication
In this lesson, we will learn the basics of claims-based authorization and how it is implemented in ASP.NET Core.
Like all modern frameworks, ASP.NET Core authorizes access to resources using claims. Claims are assertions about the subject that needs to access the resources. They are obtained through a process called authentication, which is defined in manifests called authentication schemes.
Authentication and authentication schemes
Each authentication scheme specifies the kind of action needed for authenticating a user and to compute its claims. Authentication must not be confused with login. In fact, login is the process of obtaining credentials for being authenticated in subsequent requests, while authentication is the process of validating these credentials on each request, and of extracting claims from them. The application that issues your credentials can be different from the application where we use these credentials to authenticate.
Typical credentials used by web applications are cookies and JWT (JSON Web Token) tokens.
Cookies must be necessarily emitted by the same web application where we need to authenticate as cookies cannot be used in cross-site calls. However, this doesn’t mean the user must log in to the same website where we need to authenticate. Protocols like OpenId and OAuth2 enable users to log in to an application that plays the role of identifying the provider to get a cookie for another application. These protocols work by redirecting the user browser between the two websites with information and security tokens passed in the query string URLs during these redirects. In other words, the two websites communicate through the user browser.
JWTs, on the other hand, do not have the same limitations as cookies. They are strings received from the identity provider application that we must include in all request headers, as shown below:
Authorization: Bearer <bearer token string>
The advantage of cookies is that they are automatically sent by the browser in each request to their target URLs, while JWT must be manually included each time in the request headers. That’s why usual browser requests to web servers must necessarily use cookies as browsers do not automatically include the headers needed by JWT credentials.
JWT tokens are preferred for authenticating requests to web APIs from browsers through AJAX or from native applications through HTTP clients. Web APIs prefer JWTs to cookies as they are not vulnerable to XSRF attacks (see the previous chapter on validation). In fact, XSRF attacks rely on cookies being automatically sent in all requests to their target URLs, while JWTs are not automatically sent.
Both authentication cookies and JWTs store all claims serialized and encrypted/signed so they can’t be modified, together with other information, such as their expiration date and their issuer. Therefore, authenticating them just requires some cryptographic and deserialization jobs together with expiration and issuer verifications.
Challenge and forbid actions
Authentication schemes specify what happens when an unauthenticated user tries to access a protected resource (challenge action), and when an authenticated user fails to access a protected resource for lack of privileges (forbid action).
The challenge action of all cookie-based authentication schemes is to redirect the unauthenticated user to the login page. The challenge action of all JWT-based authentication schemes is to return a 401
code with a www-authenticate: bearer
header to inform the client that it needs to authenticate with a JWT.
The forbid action of cookie-based authentication schemes is to redirect to a “Forbidden” page that informs the user about the lack of the needed privileges. The forbid action of all JWT-based authentication schemes is to simply return a 403
error code.
Anatomy of claims, identities, and principals
Claims basically are value-name pairs. The name part of the pair is called claim type, and it can be public or private. Public claim types can’t create ambiguities and consequently can be used across different websites without limitations since they have a URI format that ensures their unicity. In other words, since they contain a domain part that is specific for the organization that issues the claim they can’t collide with the claims of other organizations. Private claims are generic strings with no format constraints.
In .NET, claims are represented by the Claims
class that is contained in the System.Security.Claims
namespace.
The main properties of the Claim
class are:
Property name | Description |
---|---|
Type | A string containing the claim type |
Value | A string with the claim value |
ValueType | An optional string that specifies a target type for deserializing the claim value |
Issuer | A string representing the organization that issued the claim |
The ClaimTypes
static class in the same System.Security.Claims
namespace contains several standard claim types in its static properties, including:
Property | Description |
---|---|
ClaimTypes.Name | Represents the username |
ClaimTypes.Role | Represents a role assigned to the user |
ClaimTypes.GivenName | Represents the user first name |
ClaimTypes.Surname | Represents the user surname |
A subject may be assigned several occurrences of the same claim type with different values. This is typical of the role claim type since the same user can cover several roles, such as DB administrator, website administrator, etc.
When a user authenticates with a given authentication scheme, an object that represents the identity of the subject according to that authentication scheme is created. This object contains the list of all assigned claims, together with the authentication scheme name.
ASP.NET Core applications use the ClaimsIdentity
class to represent identities. This class contains a Claims
property with all claims and an AuthenticationType
string property that contains the name of the authentication scheme used for authenticating the request.
ClaimsIdentity
also contains read-only computed properties, such as IsAuthenticated
that returns true whenever an authorization scheme authorizes that identity. This property is useful because unauthenticated requests do not contain a null HttpContext.User
but an unauthorized user with an unauthorized ClaimsIdentity
. ClaimsIdentity
also contains a Name
read-only property that reflects the value of the ClaimTypes.Name
claim that is the username.
HttpContext.User
, that is the user assigned to the current request, is not a ClaimsIdentity
but a ClaimsPrincipal
, a more complex object that may potentially contain several ClaimsIdentity
instances. In fact, the same request may be simultaneously authenticated by several authentication schemes, and so the request HttpContext.User
must be a type that can contain several ClaimsIdentity
instances, as summarized in the picture below:
Get hands-on with 1400+ tech skills courses.