1. Authorization flow with PKCE
When a browser is involved in the communication, it is not safe to store the id and access tokens in the it. Browser is not considered to be safe. Everything you send to the browser is readable and can be extracted, manipulated, and potentially exploited. That's why authorization code flow was invented.
Using that flow, tokens aren't sent in the redirect back to the client from the authorization endpoint. Instead, a code is sent. The code can then be used by the client to do a back channel request. That's a request done at the client level the browser doesn't know about. In this request, the code is exchanged for an access token at a token endpoint. When the client does the request to the token endpoint, it has to present its client ID and secret. If the OpenID scope is among the requested scopes, the token endpoint also sends the identity token in the response.
But there's a problem. Under certain conditions, attackers could intercept the code and use it to get the tokens at the token endpoint. It's called a code substitution attack. That's why PKCE, and add‑on to the existing authorization code flow, was invented. It is pronounced pixie. Every time the client does a redirect to the authorization endpoint, it generates a secret. When the identity provider hands out the code, it remembers that this secret belongs to the issued code. It's can either be cryptographically merged with a code, or the identity provider can store the association in a data store. Now, when the code gets exchanged at the token endpoint, the identity provider checks if secret and code still match. Should attackers get the code, it would be very difficult for them to also obtain the secret needed to exchange the code for tokens.
2.Inside configureServices we need to configure the
a. API resources -- the API's that the IDP protects.
public static IEnumerable<ApiResource> Apis =>
new ApiResource[]
{ ""APIONE};
b. Clients: The clients that request resources or scopes . They can request access to API's.
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
// this will be displayed on the consent screen
ClientName="Image Gallery",
ClientId="imagegalleryclient",
// type of flow
AllowedGrantTypes=GrantTypes.Code,
RequirePkce=true,
// were to redirect on the client after signin
RedirectUris={
"https://localhost:44389/signin-oidc"
},
// this will logout the user and redirect to the IDP which contains a link to redirect back to the client app
PostLogoutRedirectUris={ "https://localhost:44389/signout-callback-oidc" },
// wat are the scopes this client can request
AllowedScopes= {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
ClientSecrets= { new Secret("secret".ToSha256()) }
}
};
c. Resources: They map to a list of claims. The client can request for a set of Resources or claims
public static IEnumerable<IdentityResource> Ids =>
new IdentityResource[]
{
//subjectId
new IdentityResources.OpenId(),
//Givenname and family name
new IdentityResources.Profile()
};
d. USERS: Users login to the system using the client and access the API resources and other resources based on the Allowed scopes
public static List<TestUser> Users = new List<TestUser>
{
new TestUser{SubjectId = "818727", Username = "alice", Password = "alice",
Claims =
{
new Claim(JwtClaimTypes.Name, "Alice Smith"),
new Claim(JwtClaimTypes.GivenName, "Alice"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json)
}
},
new TestUser{SubjectId = "88421113", Username = "bob", Password = "bob",
Claims =
{
new Claim(JwtClaimTypes.Name, "Bob Smith"),
new Claim(JwtClaimTypes.GivenName, "Bob"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "BobSmith@email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json),
new Claim("location", "somewhere")
}
}
};
When a client request for the OpenID and Profile scope they get the SubjectID and FamilyName, FullName claims of the user. The User will be able to approve the access to the claims from the consent screen.
Inside ConfigureServices configure the Auth middleware to use OIDC
services.AddIdentityServer()
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddInMemoryIdentityResources(Config.Ids)
.AddTestUsers(TestUsers.Users)
.AddDeveloperSigningCredential();
Inside Configure
app.UseIdentityServer(); //internally calls useAuthentication
app.UseAuthorization();
install System.Security.Principal.Windows from nuget
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, o =>
{
o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.Authority = "https://localhost:44350/";
o.ClientId = "imagegalleryclient";
o.ClientSecret = "secret";
o.ResponseType = "code";
o.UsePkce = true;
//we can add this wen we want to change the RedirectUris configured in the IDP
//o.CallbackPath = "";
o.Scope.Add("openid");
o.Scope.Add("profile");
o.SaveTokens = true;
});
Even if we logout, we stay at the IDP and we get a link to return to the client. To turn autoredirect upon login. Go to AccountOptions.cs and turn on AutomaticRedirectAfterSignOut=true
From the token endpoint we get back an access token. As shown below
For using this flow all we need to do is
o.GetClaimsFromUserInfoEndpoint = true;
inside the client IODC configuration inside ConfigureServices method.
Since the client has allowed scopes set to OPENID and PROFILE
AllowedScopes= {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
The claims associated to the Profile scope , that is givenname and familyname are returned. These claims are added to the User object and will not be present in the IDToken.
To request additional claims call the UserInfoEndpoint as follows
var client = _httpClientFactory.CreateClient("IDPClient");
var discoverDocument = await client.GetDiscoveryDocumentAsync();
var token = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
var userInfoRes = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = discoverDocument.UserInfoEndpoint,
Token = token
});
var claims = userInfoRes.Claims;
But we need to configure our clients and IDP, create a new IdentityResource for address
public static IEnumerable<IdentityResource> Ids =>
new IdentityResource[]
{
//subjectId
new IdentityResources.OpenId(),
//Givenname and family name
new IdentityResources.Profile(),
new IdentityResources.Address()
};
. Next add the resource in the requested scopes for the client.
AllowedScopes= {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Address
},
Now in the client configuration add the new scope as follows
o.Scope.Add("address");
o.ClaimActions.DeleteClaim("amr");
2. Attribute based access control (ABAC)
ABAC is preferred over RBAC
Add new IdentityResource
new IdentityResource(
"userrole", // name
"Role for the user", // name to be displayed
new List<string> { "role"} // type that will be returned
)
Add new role claims for both users
new Claim("role", "normalUser")
new Claim("role", "payingUser")
Request for the new scope from the client
o.Scope.Add("userrole");
Map the custom claim from the token to the claims principal. Request for the userrolescope and map the claim role to the claims principal
//To map the newly added claim to claims principal
o.ClaimActions.MapUniqueJsonKey("role", "role");
but we need to configure IsInRole to check against our role claim
when we use this condition set role name to the role claim name in token validation parameters
This will just hide the button, but we can directly navigate to the address from the browser,
*@
@if (User.IsInRole("payingUser"))
{
<li><a asp-area="" asp-controller="Gallery" asp-action="Sample">UserInfo Sample</a></li>
}
Configure the client to check for the role claim from the custom role claim returned
o.TokenValidationParameters = new TokenValidationParameters
{
//NameClaimType=JwtClaimTypes.GivenName,
RoleClaimType="role"
};
We have just hidden our button but the user can directly entire by changing the url in the browser.
So to prevent that add the following code to the contoller
[Authorize(Roles = "payingUser, sampleUser, admin")]
multiple roles can be specified. If the user is not in one of the specified roles then we redirect to the access denied page.
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,o=> {
o.AccessDeniedPath = "/AuthorizationOptions/AccessDenied";
})
Create a controller and a view with the same path
It will contain a "client_id": "imagegalleryclient",
AuthorizationCodeLifetime=300secs
AccessTokenLifetime=3600 or 1 hour
We can use these 3 properties to set the life time of the tokens. The IDP sets a skew time for each token, that is if we set the access token lifetime for 2 mins then the token exprires after 7 mins. A skew time of 5 mins is used by the IDP to sync with the different timezones.
Rather than having the client enter the credentials and login again everytime the access token expires, we cam request for "offline_access" or the use of reference tokens. The client app has to authenticate itself by passing the clientid and secret. in the request body it passes the refresh token, grant type. Once the token is validated we get back new set of tokens.
AllowOfflineAccess=true,
//AbsoluteRefreshTokenLifetime=1 day
we can change the scheme of the life time b
UpdateAccessTokenClaimsOnRefresh=true,
This is done to refresh the claims when we get a new refresh token.
o.Scope.Add("offline_access");
AUTHORIZATION FLOW WITH PKCE (IMPLEMENTATION)
The word token means requesting the access token.
Our web application creates an authentication request with response type code, and potentially other parameters like scopes. The web app sends the request, which means it simply redirects to the URI. At level of the identity provider, the user authenticates, for example, by providing a username and password combination. Optionally, the identity provider asks for consent, i.e., it'll ask you if you want to allow the application to get access to your profile information. The IDP then sends us back to the web app via URI redirection or by a form post. It includes the authorization code in this case. That's the response type we asked for. The code is thus delivered via the URI, that is called front channel communication. This code can be seen as a very short‑lived proof of authentication, linked to the user that just signed into the identity provider.
It thus binds the frontend browser session to the backend session between client and identity provider. The web client then calls the token endpoint via the middleware through the back channel. This is thus a request that doesn't use browser redirection. For this, it passes through the authorization code and client authentication. Most often, that's clientid and a clientsecret. In other words, the client application has to authenticate itself. In the response, we get back an identity token. This token is validated.
After the IDTOken is validated a claims principal is created and an encrypted cookie is set in the browser. This cookie is sent to the app in every request , through this cookie the claims principal is constructed.
CREATING THE IDP
1. create an empty project and add IdentityServer4 from nuget.2.Inside configureServices we need to configure the
a. API resources -- the API's that the IDP protects.
public static IEnumerable<ApiResource> Apis =>
new ApiResource[]
{ ""APIONE};
b. Clients: The clients that request resources or scopes . They can request access to API's.
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
// this will be displayed on the consent screen
ClientName="Image Gallery",
ClientId="imagegalleryclient",
// type of flow
AllowedGrantTypes=GrantTypes.Code,
RequirePkce=true,
// were to redirect on the client after signin
RedirectUris={
"https://localhost:44389/signin-oidc"
},
// this will logout the user and redirect to the IDP which contains a link to redirect back to the client app
PostLogoutRedirectUris={ "https://localhost:44389/signout-callback-oidc" },
// wat are the scopes this client can request
AllowedScopes= {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
ClientSecrets= { new Secret("secret".ToSha256()) }
}
};
c. Resources: They map to a list of claims. The client can request for a set of Resources or claims
public static IEnumerable<IdentityResource> Ids =>
new IdentityResource[]
{
//subjectId
new IdentityResources.OpenId(),
//Givenname and family name
new IdentityResources.Profile()
};
d. USERS: Users login to the system using the client and access the API resources and other resources based on the Allowed scopes
public static List<TestUser> Users = new List<TestUser>
{
new TestUser{SubjectId = "818727", Username = "alice", Password = "alice",
Claims =
{
new Claim(JwtClaimTypes.Name, "Alice Smith"),
new Claim(JwtClaimTypes.GivenName, "Alice"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json)
}
},
new TestUser{SubjectId = "88421113", Username = "bob", Password = "bob",
Claims =
{
new Claim(JwtClaimTypes.Name, "Bob Smith"),
new Claim(JwtClaimTypes.GivenName, "Bob"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "BobSmith@email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json),
new Claim("location", "somewhere")
}
}
};
When a client request for the OpenID and Profile scope they get the SubjectID and FamilyName, FullName claims of the user. The User will be able to approve the access to the claims from the consent screen.
Inside ConfigureServices configure the Auth middleware to use OIDC
services.AddIdentityServer()
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddInMemoryIdentityResources(Config.Ids)
.AddTestUsers(TestUsers.Users)
.AddDeveloperSigningCredential();
Inside Configure
app.UseIdentityServer(); //internally calls useAuthentication
app.UseAuthorization();
Adding a UI for OIDC
Copy the QuickStart, Views and wwwroot folder from https://github.com/IdentityServer/IdentityServer4.Quickstart.UI to the IDP project.
app.UseStaticFiles(); // add this else bootstrap and static contents wont work
services.AddControllersWithViews();
Client Configuration
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, o =>
{
o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.Authority = "https://localhost:44350/";
o.ClientId = "imagegalleryclient";
o.ClientSecret = "secret";
o.ResponseType = "code";
o.UsePkce = true;
//we can add this wen we want to change the RedirectUris configured in the IDP
//o.CallbackPath = "";
o.Scope.Add("openid");
o.Scope.Add("profile");
o.SaveTokens = true;
});
Logout
[HttpGet("Logout")]
public async Task Logout() {
//this will logout from the cookie scheme (clear the cookie from the browser). But we are logged in at the IDP
// so another request will again get authenticated.
await this.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
// this will cause the user to sign out from the IDP. THis is used as the default challenge scheme
await this.HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
}
Even if we logout, we stay at the IDP and we get a link to return to the client. To turn autoredirect upon login. Go to AccountOptions.cs and turn on AutomaticRedirectAfterSignOut=true
Getting extra claims for the user
The IDToken doesnt contain all the claims as the size of the token becomes large. So we call the token endpoint and we get back an access token along with the IDToken. This access token is sent tot the UserInfoEndpoint where the token is validated and the claims for the user are returned.From the token endpoint we get back an access token. As shown below
For using this flow all we need to do is
o.GetClaimsFromUserInfoEndpoint = true;
inside the client IODC configuration inside ConfigureServices method.
Since the client has allowed scopes set to OPENID and PROFILE
AllowedScopes= {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
The claims associated to the Profile scope , that is givenname and familyname are returned. These claims are added to the User object and will not be present in the IDToken.
Getting Extra claims
We can avoid having all the claims inside the IDToken and instead we can get the necessary claims by calling the UserInfoEndpoint. This way the size of the token is smaller and we call the UserInfo only when we need extra claims.
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, o =>
{
o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.Authority = "https://localhost:44350/";
o.ClientId = "imagegalleryclient";
o.ClientSecret = "secret";
o.ResponseType = "code";
o.UsePkce = true;
//we can add this wen we want to change the RedirectUris configured in the IDP
//o.CallbackPath = "";
//Request these from the userinfo endpoint
o.Scope.Add("openid");
o.Scope.Add("profile");
o.Scope.Add("address");
// this is to remove the nbf from the identity token.
//TO make the token smaller remove claims that are not needed.
o.ClaimActions.Remove("nbf");
// call userinfoendpoint to get extra claims
// this is done to make the IDTcoken smaller.
o.GetClaimsFromUserInfoEndpoint = true;
o.SaveTokens = true;
});
To request additional claims call the UserInfoEndpoint as follows
var client = _httpClientFactory.CreateClient("IDPClient");
var discoverDocument = await client.GetDiscoveryDocumentAsync();
var token = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
var userInfoRes = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = discoverDocument.UserInfoEndpoint,
Token = token
});
var claims = userInfoRes.Claims;
But we need to configure our clients and IDP, create a new IdentityResource for address
public static IEnumerable<IdentityResource> Ids =>
new IdentityResource[]
{
//subjectId
new IdentityResources.OpenId(),
//Givenname and family name
new IdentityResources.Profile(),
new IdentityResources.Address()
};
. Next add the resource in the requested scopes for the client.
AllowedScopes= {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Address
},
Now in the client configuration add the new scope as follows
o.Scope.Add("address");
To remove a claim from the claimsPrincipal
//remove from Claims pricipalo.ClaimActions.DeleteClaim("amr");
Authorization
1. Role based access control (RBAC)2. Attribute based access control (ABAC)
ABAC is preferred over RBAC
RBAC
Client
AllowedScopes= {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Address,
// make the client request for access to the users role claim
"userrole"
},
Add new IdentityResource
new IdentityResource(
"userrole", // name
"Role for the user", // name to be displayed
new List<string> { "role"} // type that will be returned
)
Add new role claims for both users
new Claim("role", "normalUser")
new Claim("role", "payingUser")
Request for the new scope from the client
o.Scope.Add("userrole");
Map the custom claim from the token to the claims principal. Request for the userrolescope and map the claim role to the claims principal
//To map the newly added claim to claims principal
o.ClaimActions.MapUniqueJsonKey("role", "role");
Hide the button based on the role.
@*this will check if the user role is has the value paying userbut we need to configure IsInRole to check against our role claim
when we use this condition set role name to the role claim name in token validation parameters
This will just hide the button, but we can directly navigate to the address from the browser,
*@
@if (User.IsInRole("payingUser"))
{
<li><a asp-area="" asp-controller="Gallery" asp-action="Sample">UserInfo Sample</a></li>
}
Configure the client to check for the role claim from the custom role claim returned
o.TokenValidationParameters = new TokenValidationParameters
{
//NameClaimType=JwtClaimTypes.GivenName,
RoleClaimType="role"
};
We have just hidden our button but the user can directly entire by changing the url in the browser.
So to prevent that add the following code to the contoller
[Authorize(Roles = "payingUser, sampleUser, admin")]
multiple roles can be specified. If the user is not in one of the specified roles then we redirect to the access denied page.
Adding an access denied page
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,o=> {
o.AccessDeniedPath = "/AuthorizationOptions/AccessDenied";
})
Create a controller and a view with the same path
Access Token
The access token contains a field called audience. Unlike the IDToken the audience here is the api that we try to call. That is why we configure the JWT bearer to check if the audience field has the api name.It will contain a "client_id": "imagegalleryclient",
Authorization flow with PKCE in detail
Our web application creates a random string called a code_verifier. The web app then hashes that code_verifier, and that hashed version is called the code_challenge. Then the web app creates an authentication request with response‑type code, and it includes the code_challenge. The web app sends the request to the authorization endpoint at level of the identity provider. At that level, the code_challenge is store. Then the user authenticates, and the IdP optionally asks for consent. After that, the identity provider redirects back to the web application with the authorization code in the URI. The web application then calls the token endpoint, including client authentication, and it passes through the authorization code and code_verifier as well.
The identity provider hashes this, and it checks if it matches the stored code_challenge. The identity provider will only return tokens when this matches. We get back an access_token and an identity token. The identity token is validated. Part of this validation is calculating the hash from the access_token to see if it matches the AD hash value in the identity token. So the access_token takes part in validation of the identity token. If validation checks out, the claims identity is created from the identity token, and that's used to sign into our ASP.NET Core MVC app. We've also got an access token now. Optionally, as we know, the UserInfo endpoint can be called for additional user information. So the access_token is stored, and on each request to the API, the token is included as a bearer token value for the Authorization request header. At level of the API, the access_token is validated.
Secure the API
IdentityServer4.AccessTokenValidation on the API.
1. Create new API resource on the IDP
new ApiResource[]
{
new ApiResource("imageGalleryApi","Image gallery api")
};
2. Add this resource to the clients allowed scopes
AllowedScopes= {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Address,
// make the client request for access to the users role claim
"userrole",
"imageGalleryApi"
},
3. Configure client project to request access to this resource
//request access to the api resource
o.Scope.Add("imageGalleryApi");
4. Configure Api to setup authentication and validate the bearer token
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(o =>
{
o.Authority = "https://localhost:44350/";
o.ApiName = "imageGalleryApi";
});
Add authorize globally to all the controllers
services.AddControllers(o =>
{
o.Filters.Add(new AuthorizeFilter());
})
Setup a handler that add the access token as a bearer token in each request
We create this handler and register this with the httpclient to call the API's. This will add the automatically get the token and add it as a bearer token.
public class HttpRequestHandler : DelegatingHandler
{
public HttpRequestHandler(IHttpContextAccessor httpContextAccessor)
{
HttpContextAccessor = httpContextAccessor;
}
public IHttpContextAccessor HttpContextAccessor { get; }
/// <summary>
/// Get the token and add it to the HttpRequest
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = await HttpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken)
?? throw new Exception("Access token not present in the context");
request.SetBearerToken(token);
return await base.SendAsync(request, cancellationToken);
}
Redirect to Unauthorized page if Unauthorized
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized
|| response.StatusCode == System.Net.HttpStatusCode.Forbidden)
return RedirectToAction("AccessDenied", "AuthorizationOptions");
The User object in the client is constructed from the IDToken whereas the User in the API is constructed from the Access Token
Set mandatory claims while requesting access for the API Resource
new ApiResource(
"imageGalleryApi",
"Image gallery api" ,
new List<string>{ "role"} // these roles will need to be requested when requesting this resource
)
Only PayingUSers can upload an image -- check commits
RBAC VS ABAC
we can write more complex logic for access control. Ex: user who has age > 18 and state is kerala etc
ABAC
2 new claims for both users
//for new policy
new Claim("surname","perera"),
new Claim("pob","oolampara")
1 new Identity Resource, which will return 2 claims when requested
//ABAC
new IdentityResource(
"perosonaldetails",
"Achante Perum Naadum",
new List<string>
{
"surname",
"pob"
})
Client Requests this identity Resource
//ABAC
o.Scope.Add("perosonaldetails");
Then we map both the claims to the ClaimsPrincipal
o.ClaimActions.MapUniqueJsonKey("surname", "surname");
o.ClaimActions.MapUniqueJsonKey("pob", "pob");
Setting up a policy
services.AddAuthorization(op =>
{
op.AddPolicy("fromandsurname",policy=> {
policy.RequireClaim("surname", "sasi", "soman", "chandy");
policy.RequireClaim("pob", "kunnamkulam", "oachira");
});
});
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService
@if ((await AuthorizationService.AuthorizeAsync(User, "fromandsurname")).Succeeded)
{
<li><a asp-area="" asp-controller="Gallery" asp-action="AddImage">Add an image</a></li>
}
Authorization Handlers
First we need to create a requirement and then create a handler for that requirement
Token Life TIme
IdentityTokenLifetime=400 secs;AuthorizationCodeLifetime=300secs
AccessTokenLifetime=3600 or 1 hour
We can use these 3 properties to set the life time of the tokens. The IDP sets a skew time for each token, that is if we set the access token lifetime for 2 mins then the token exprires after 7 mins. A skew time of 5 mins is used by the IDP to sync with the different timezones.
Rather than having the client enter the credentials and login again everytime the access token expires, we cam request for "offline_access" or the use of reference tokens. The client app has to authenticate itself by passing the clientid and secret. in the request body it passes the refresh token, grant type. Once the token is validated we get back new set of tokens.
AllowOfflineAccess=true,
//AbsoluteRefreshTokenLifetime=1 day
we can change the scheme of the life time b
UpdateAccessTokenClaimsOnRefresh=true,
This is done to refresh the claims when we get a new refresh token.
client configuration
//for refresh tokeno.Scope.Add("offline_access");
Comments
Post a Comment