47
loading...
This website collects cookies to deliver better user experience
.
eyJleHAiOjE2MjQzNzYwODIsImlhdCI6MTYyNDM3NTQ4MiwiYXV0aF90aW1lIjoxNjI0Mzc0Mjc2LCJqdGkiOiJmZjQ2YzJmNy1hNjkxLTQzNTQtOTUzNC1lODY4YTA4YzkzNGQiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvQXV0aERlbW9SZWFsbSIsImF1ZCI6ImF1dGgtZGVtby13ZWItYXBpIiwic3ViIjoiYjEyMWEzNDMtYTk1OC00MTJhLTg3YzAtNzFhNGE3NmRmNTBhIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYXV0aC1kZW1vLXNwYSIsIm5vbmNlIjoiNjgzNTY3YWVmZjk5NDcyYmRlNzJiZDgyMDk4MGE0NTY2N3RSQWRrMTgiLCJzZXNzaW9uX3N0YXRlIjoiNTMyZDExZWEtZTU1My00Mjc2LWFiMDItYThkNjRjOWU2OWE5IiwiYWNyIjoiMCIsInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6IkRlbW8gVXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6ImRlbW8udXNlciIsImdpdmVuX25hbWUiOiJEZW1vIiwiZmFtaWx5X25hbWUiOiJVc2VyIiwiZW1haWwiOiJkZW1vLnVzZXJAZW1haWwuY29tIn0.
U3qv-ujxq5axhLYeEON4ofDmd2CH_RLDhY3KBK8gNJkAYIx3dhCMI-4mNjIPxkpXjXdF1Ci7fX2zM9AN8_d2nVhYQB7dusdvjxRAfzu_IzAPhl4hZXNxEIJYd2f6KBVU_gnSKgJyEi5LJ89blYIllbyN5KfPke_DIZgL3CfhUiAGqE5eW7UY1weOTjcGsV29u5vv_FcONmk2z_uTDH9qN7g-xTVkEv3tr-u7osK4T8fwoxWA62TlTyxefr_ZsDDyy3nGCL_YhDTdzqASs5_Xc60vaP0x3BmdAHXM4-p0xgei6qOv9g4FYy_3u1DUAnoXY6g-Nls-MVs1K18f8H2ZfA{
"alg": "RS256",
"typ": "JWT",
"kid": "4eaR-RSKd0txWz6RZtvMTNk0rVEgpTvBwtBXKliJ1Qw"
}
{
"exp": 1624376082,
"iat": 1624375482,
"auth_time": 1624374276,
"jti": "ff46c2f7-a691-4354-9534-e868a08c934d",
"iss": "http://localhost:8080/auth/realms/AuthDemoRealm",
"aud": "auth-demo-web-api",
"sub": "b121a343-a958-412a-87c0-71a4a76df50a",
"typ": "Bearer",
"azp": "auth-demo-spa",
"nonce": "683567aeff99472bde72bd820980a45667tRAdk18",
"session_state": "532d11ea-e553-4276-ab02-a8d64c9e69a9",
"acr": "0",
"scope": "openid email profile",
"email_verified": true,
"name": "Demo User",
"preferred_username": "demo.user",
"given_name": "Demo",
"family_name": "User",
"email": "[email protected]"
}
scope
claim is a special one that needs a bit of attention. A scope is a grouping claims and it's requested by the client app. In the example above given_name
and family_name
claims are present because the client app requested the profile
scope. Just because the client requested a scope doesn't mean the auth server will provide the claims for that scope in the payload, it depends on many factors. For example the scope maybe configured on the authorisation server in a way that it requires to present consent screen to the user and will only be provided if user has given consent. The scope
claim in the JWT payload basically tells us which scopes have been provided in the payload, in this case openid
(which is the standard scope for OpenID Connect, must be preset), email
and profile
. trade_license
scope, which adds claims trade_license_number
and trade_license_expiry
to the token. When the clien app requests for the trade_license
scope, user is asked during login: This application wants to access your trade license information. Do you accept? [yes | no].
trade_license
scope is not provided as part of the scope
claim, trade_license_number
and trade_license_expiry
claims are not provided with the payload. If user answers yes these claims are provided. .well-known
endpoint called jwks_uri
. Since the resource server is not dependent on a shared key, this enables a key-rotation mechanism, ensuring that the private-public keys are rotated regularly to ensure extra security against key leaks. iss
(issuer) claim to ensure that the token has been issued by the right auth server.aud
(audience) claim to ensure that the resource server is one of the audiences.jwks_uri
. services
.AddAuthentication()
.AddJwtBearer(x =>
{
x.MetadataAddress = "http://keycloak:8080/auth/realms/AuthDemoRealm/.well-known/openid-configuration";
x.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = "auth-demo-web-api"
};
});
MetadataAddress
is set to the .well-known
endpoint. The middleware can retrieve the following required properties from the .well-known
edn-point:jwks_uri
from where it needs retrieve the public key to validate the token signature. services.AddAuthorization(o =>
{
o.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireClaim("email_verified", "true")
.Build();
});
Authorize
attribute on a controller or action will return unauthorised if the token doesn't have the claim email_verified
with value true
. Here's an example of a controller that's using the default plicy to authorise:[ApiController]
[Route("[controller]")]
[Authorize]
public class EamilController : ControllerBase
{
// ...
}
services.AddAuthorization(o =>
{
o.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireClaim("email_verified", "true")
.Build();
var tradieAuthorizerPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireClaim("trade_license_number")
.Build();
o.AddPolicy("TradieOnly", tradieAuthorizerPolicy);
});
Policy
value of the Authorization
attribute. In the example below anyone can post a job, but only tradies, with a trade_license_number as per the policy configuration above, are authorised to apply:[ApiController]
[Route("[controller]")]
[Authorize()]
public class JobsController : ControllerBase
{
[Authorize( Policy = "TradieOnly")]
public async Task Apply(JobApplication jobApplication)
{
// ...
}
public async Task PostJob(Job job)
{
// ...
}
}
Code Verifier
and a Code Challenge
hash from it..well-known
JSON for the auth server. The client app passes the following parameters as a minimum with the redirect:
Redirect URL
we passed in Step 2. The server provides an Authorisation Code
with the redirect, which is a one time code that can be used by the client app to obtain the Access Token
./token
endpoint using the provided Authorization Code
and the Code Verifier
created in step 1. The endpoint to obtain the token is usually published in the .well-known
JSON.authorization
header for restricted resources.angular-auth-oidc-client
does all of the above steps to ensure the the access token is available:oidcConfigService.withConfig({
authWellknownEndpoint: 'http://localhost:8080/auth/realms/AuthDemoRealm/.well-known/openid-configuration',
redirectUrl: `${window.location.origin}/home`,
clientId: 'auth-demo-spa',
scope: 'openid profile email',
responseType: 'code',
triggerAuthorizationResultEvent: true,
postLogoutRedirectUri: `${window.location.origin}/home`,
startCheckSession: false,
postLoginRoute: '',
unauthorizedRoute: '/unauthorized',
logLevel: LogLevel.Debug
});
.well-known
URI here. The client library should know the following important endpoints from here:
openid
for OpenID Connect claims. code
for Authorisation Code Flow.login()
and logout()
methods hooked to the login/logout buttons.login() {
this.oidcSecurityService.authorize();
}
logout() {
this.oidcSecurityService.logoff();
}
let apiUrl = `${environment.baseApiUrl}/weatherforecast`;
let accessToken = this.oidcSecurityService.getToken();
let headers = {};
if(accessToken)
headers['authorization'] = `Bearer ${accessToken}`;
this.data = await this.httpClient
.get(apiUrl, {
headers: headers
})
.pipe(take(1))
.toPromise();
services
.AddAuthentication()
.AddJwtBearer(x =>
{
x.MetadataAddress = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
x.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = "auth-demo-web-api"
};
});
oidcConfigService.withConfig({
authWellknownEndpoint: 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration',
redirectUrl: `${window.location.origin}/home`, // need to configure
clientId: 'auth-demo-spa', // need to configure
scope: 'openid profile email',
responseType: 'code',
triggerAuthorizationResultEvent: true,
postLogoutRedirectUri: `${window.location.origin}/home`,
startCheckSession: false,
postLoginRoute: '',
unauthorizedRoute: '/unauthorized',
logLevel: LogLevel.Debug
});
.well-known
endpoint, configure the server and the client and you're good to go.