Apr 14th, 2017 - written by Kimserey with .
Few week ago I described how to build a custom Jwt authentication. Today I will show how we can use Identity server together with Resource owner password flow to authenticate and authorise your client to access your api.
The full source code is available on my GitHub https://github.com/Kimserey/identity-server-test.
Identity server is a framework which implements Open ID Connect and OAuth 2.0 protocols. The purpose of Identity server is to centralize the identity management and at the same time decouple your api(s) from authentication and authorization logic. Centralizing has many advantages:
There are many more advantages like the Open ID connect protocol implementation which handles consents and the handling of different authentication flows.
In this post, we will be looking at the Resource owner password flow
. It is the simplest flow but comes with two disavantages:
The identity provider is a server responsible for holding all identities and providing access tokens which can be used to access protected resources. The api/identity resources are the resources that you wish to protect. Api resources would be apis that you wish to protect, grant access to only certain clients. Identity resources would be pieces of information from the identity itself that you wish to protect, like the address, the name or date of birth contained in the identity for example.
What the identity provider will provide an access token which can be used to access either the Apis or the identity information. The identity information can be retrieved from the UserInfo
endpoint on the identity provider. We will see next that we can configure the middleware in the client to authomatically retrieve the identity claims by setting the property GetClaimsFromUserInfoEndpoint
to true.
In this example, we will have 3 pieces:
So let’s start by configuring the identity provider. First we create an empty asp.net core project and add identityServer4 package.
Then from the Startup file we register the identity service and add the middleware.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// -- This is in the Identity provider
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//... some other services here
services.AddIdentityServer()
.AddInMemoryApiResources(Configs.GetApiResources())
.AddInMemoryClients(Configs.GetClients())
.AddInMemoryIdentityResources(Configs.GetIdentityResources())
.AddTestUsers(Configs.GetTestUsers())
.AddTemporarySigningCredential();
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
//... some code here
app.UseIdentityServer();
}
}
From the service registration, we can already see that we will need to give the configuration of our Api resources
, Clients
, Identity resources
and some test users.
So next we can create a configuration file which will hold identity server configurations.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// -- This is in the Identity provider
public class Configs
{
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>();
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>();
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>();
}
public static List<TestUser> GetTestUsers()
{
return new List<TestUser> {
new TestUser {
SubjectId = "1",
Username = "alice",
Password = "password"
}
};
}
}
Take note that AddTestUsers
adds a profile service
and a resource owner password validator
which we would need to provide when not using test users. We can see from the Identity Server code what AddTestUser
does:
1
2
3
4
5
6
7
8
9
// -- Code from Identity Server 4 source code
public static IIdentityServerBuilder AddTestUsers(this IIdentityServerBuilder builder, List<TestUser> users)
{
builder.Services.AddSingleton(new TestUserStore(users));
builder.AddProfileService<TestUserProfileService>(); //<--- we will need to implement this
builder.AddResourceOwnerValidator<TestUserResourceOwnerPasswordValidator>(); <-- and this
return builder;
}
Next we will protect our api.
Let’s start a web api project and add IdentityServer4.AccessTokenValidation
.
This library allows us to protect the api by using the IdentityServerAuthentication
middleware which will validate the access tokens.
So we place the following code before our MVC
middleware binding:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// -- This is in the API
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// ... some other stuff
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5000",
ApiName = "api",
ApiSecret = "secret",
AutomaticAuthenticate = true,
RequireHttpsMetadata = false,
});
app.UseMvc();
}
In the option, we specify the endpoint of the identity provider
, our api name
, the secret
to connect from the api to the identity provider via the introspection endpoint - this is useful when we use reference token as it allows us to be protected against an unauthorized request possessing a reference token and trying to check the state of the access token. More info here https://tools.ietf.org/html/rfc7662.
Now that we have configured our API and that it is now protected behind access token validation, we can register it in the identity provider in the api resource section:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// -- This is in the Identity provider
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api", "Web Api")
{
ApiSecrets =
{
new Secret("secret".Sha256())
}
}
};
}
Every ApiResource
come with a default scope which is the name of the api. For instance here I named it api
, therefore I will be able to give to a client AllowedScopes = { "api" }
which will provide an access token with Scopes = [ 'api' ]
.
Lastly what we need to do is to configure a client which will be trying to get an access token to access the API.
A client can be a website or a mobile app or a software client.
In this example I will be creating a Console App and use the IdentityModel
package to request for an access token.
Let’s first start by registering a client in the identity provider:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// -- This is in the Identity provider
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client {
ClientId = "client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = {
"api",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Email
}
}
}
}
Here we add a secret
for the client to connect to the identity provider.
We specify the flow to be ResourceOwnderPassword
which means that the user will always provide username/password
to connect. And we allow the client to connect to the api
resource. This will mean that when the identity provider generates an access token to this client, it will be able to use the access to token to authenticate on the protected API.
Earlier we talked about the introspection endpoint, if we want to change the Jwt token to a reference token, this can be done by setting on the Client
the property:
1
AccessTokenType = AccessTokenType.Reference
If we change that, the token generated will be a reference token - an opaque token - which allows to authenticate on the API and from the API, it will consult the identity provider given its API secret and the reference token to get access to the identity of the user. The advantage of this is that it is all done in a back channel and the reference token is a totally opaque token with no information in it, in contrast to the JWT token which contains some readable information (when not encrypted). An other point is that with the JWT token, the API does not consult the introspection and the token is valid during its whole lifespan whereas for the reference token, it is used to get the identity therefore on each requests, the latest authorizations can be fetched.
You must have noticed that we also allowed IdentityServerConstants.StandardScopes.OpenId
and IdentityServerConstants.StandardScopes.Email
. Those are identity resources.
We can define some properties to be retrieved from the identity:
1
2
3
4
5
6
7
8
9
10
// -- This is in the Identity provider
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Email()
};
}
The default Identity resources
englobe a set of UserClaims
to be retrieved when requesting for the identity resources.
For example, IdentityResources.Email
is defined as followed in IdentityServer4
source code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// -- Code from Identity Server 4 source code
public class Email : IdentityResource
{
public Email()
{
Name = IdentityServerConstants.StandardScopes.Email;
DisplayName = "Your email address";
Emphasize = true;
UserClaims = (Constants.ScopeToClaimsMapping[IdentityServerConstants.StandardScopes.Email].ToList());
}
}
// -- and somewhere else in Identity Server 4 source code
public static readonly Dictionary<string, IEnumerable<string>> ScopeToClaimsMapping = new Dictionary<string, IEnumerable<string>>
{
{
IdentityServerConstants.StandardScopes.Email,
new[] {
JwtClaimTypes.Email,
JwtClaimTypes.EmailVerified
}
}
}
This means that the Identity resource Email
allows to retrieve the email and verified email claims from the identity.
Next we create a console app, and add the IdentityModel
package. We then use the TokenClient to request for a token and we can then use that token to request for the data in the api.
We start by requesting a token:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
// Get the token
//
var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret");
var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("alice", "password");
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Console.WriteLine(tokenResponse.Json);
Console.WriteLine("Press any key to continue");
Console.ReadKey();
Then we use this token to access the protected data from the API:
1
2
3
4
5
6
7
// Query API with access token
//
Console.WriteLine("Querying API to get data using token");
var data = GetData(tokenResponse.AccessToken).Result;
Console.WriteLine(data);
Console.WriteLine("Press any key to continue");
Console.ReadKey();
Lastly we get the identity resources allowed from the AllowedScopes
, here we only allowed the Email
to be retrieved from the identity, from the /UserInfo
endpoint:
1
2
3
4
5
6
7
8
9
10
11
// Get identity claims from UserInfo
//
Console.WriteLine("Getting UserInfo");
var extraClaims = new UserInfoClient(disco.UserInfoEndpoint);
var identityClaims = await extraClaims.GetAsync(tokenResponse.AccessToken);
if (!tokenResponse.IsError)
{
Console.WriteLine(identityClaims.Json);
}
Console.WriteLine("Press any key to continue");
Console.ReadKey();
And that’s it, the client should be able to retrieve the access token, use it to get the protected data from the API and lastly get from the identity provider the identity resources it is allowed to get.
The full source code is available on my GitHub https://github.com/Kimserey/identity-server-test.
Today we saw how to implement a Resource owner password flow using Identity server 4. We saw the aspects needed to build an identity provider, how to protect an API and allow a client to access its data. We also saw how we could allow identity claims to be retrieved from the identity provider and how we could allow client to retrieve those. If you have any question leave it here or hit me on Twitter @Kimserey_Lam. See you next time!