-
Notifications
You must be signed in to change notification settings - Fork 0
how it works jwt authentication
For the Web Backend-For-Frontend, it has the following configuration
var authenticationScheme = "SeelansTyresWebBffAuthenticationScheme";
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(authenticationScheme, configure =>
{
configure.Authority = builder.Configuration["IdentityServer"];
configure.Audience = "SeelansTyresWebBff";
configure.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
configure.RequireHttpsMetadata = false;
});
For the Address Service, it's like this
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(configure =>
{
configure.Authority = builder.Configuration["IdentityServer"];
configure.Audience = "AddressService";
configure.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
configure.RequireHttpsMetadata = false;
});
The only major difference is the Audience
In order to explain this better, I had to log the three tokens received by the Mvc Frontend from IdentityServer4, as well as an exchanged token
As mentioned in a previous wiki page about Authentication, IdentityServer4 issues three tokens, the id_token
, access_token
and refresh_token
The purpose of the id_token
is to create a claims principal to log the user in, here's that sample
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE2ODA4MzMyMzAsImV4cCI6MTY4MDgzMzUzMCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDA1IiwiYXVkIjoic2VlbGFuc3R5cmVzbXZjZnJvbnRlbmQiLCJub25jZSI6IjYzODE2NDMwMDI0NDE5NzExMS5aVEZrTkdVNFpHRXRaREJrT0MwME1tUTNMVGt4T0RFdE9URTBObUpsTnpZME1XWTFOekE0WlRJMU1tWXRZV0ZqWWkwME5USXlMV0kyWVdRdFl6TTJNemN4Tm1JelpHTTIiLCJpYXQiOjE2ODA4MzMyMzAsImF0X2hhc2giOiJDSVVMcHJBRVZwUHRfekgyVk9aLTJRIiwic19oYXNoIjoiY1Vna2U4aDRIVHZ5Q25laHhJcWIwZyIsInNpZCI6IjgyNEJBM0ZGRTZGRDQyQ0M2NkZDNEEyRkE2QkE2OTMxIiwic3ViIjoiNmM3OWRmOWUtNjhjOS00YzdkLTY5NTgtMDhkYjM3MGNjODRjIiwiYXV0aF90aW1lIjoxNjgwODMzMjMwLCJpZHAiOiJsb2NhbCIsImFtciI6WyJwd2QiXX0.GFIV4VluRK9a9kSdEsk8oJdPmtSA7VGg0ZHfMJWSgLCZwRsGcGq_7Pdk1Ny5a4gxI7PVu7MFOAnjCzzz8cgJKa1j7PNeuDiWLuJODMLWBF3U1eYPXIsXISLYDBByhinprUGKxm44JBBSIv-PcOSdhjDngxJWRKD6pZDjlgcfqiQl_bF-405PHZAEy3JFD5SG7NGLWwkV1IOmdOzyCr_EC4_nRkJXY0LIFRMZ2VDqSa1QxetKrlFSWqJCHCvQtnk0bFkdhR3g3UqjA9jyNEjmAcfAbUPscSlohepz5KtjpRIG3vWghlwS7glt_c-aDMUDAbKbpG69GEUY80MAEqhhqw
The purpose of the access_token
is to access downstream microservices, here's a sample
eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9.eyJuYmYiOjE2ODA4MzMyMzAsImV4cCI6MTY4MDgzNjgzMCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDA1IiwiYXVkIjpbIlNlZWxhbnNUeXJlc012Y0JmZiIsImh0dHA6Ly9sb2NhbGhvc3Q6NTAwNS9yZXNvdXJjZXMiXSwiY2xpZW50X2lkIjoic2VlbGFuc3R5cmVzbXZjZnJvbnRlbmQiLCJzdWIiOiI2Yzc5ZGY5ZS02OGM5LTRjN2QtNjk1OC0wOGRiMzcwY2M4NGMiLCJhdXRoX3RpbWUiOjE2ODA4MzMyMzAsImlkcCI6ImxvY2FsIiwianRpIjoiNkI2NjE4RDA4MkMzOTMxMURBRDk0Q0VERThCNkYyNEQiLCJzaWQiOiI4MjRCQTNGRkU2RkQ0MkNDNjZGQzRBMkZBNkJBNjkzMSIsImlhdCI6MTY4MDgzMzIzMCwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSIsIlNlZWxhbnNUeXJlc012Y0JmZi5mdWxsYWNjZXNzIiwicm9sZSIsIm9mZmxpbmVfYWNjZXNzIl0sImFtciI6WyJwd2QiXX0.nInvNEblI4x09QtZeAeUCEjv7SfLH7WUiMy09qn7jOszmcVjSfHiLkDJHPeSrdVPv5TtxFsnWx8Turn354leg9kl7FheqFIsfaU_lmyVNtsMuBXhwmX05gRWRWmaTn9KV47ixHPgFcvC4kZ3PWWZzKJQ3OkdPKjX3VYNdnEQkaqbpFCzLYuqbdZFZQHWWqIUyQP2ApR7B23JvMxuedTjo2txu3n1UXxjjDdQ2HjPw0ReHfAn9t0AeImeQ91sdVKmpIH3tHQRW8VPyHDuxRBFugZT3OepIBPPvTKU0CR8uK9zg4puFGoPKqy-LAyQEituDcbiWRZJw1FNsUQVtshVOw
And lastly, the refresh_token
. This is issued when the client requests offline_access
as a scope and is used to retrieve a new access token when one expires and is NOT a json web token [JWT]
4243C270351289A9544716EE81B3F468D03189274964E76F6E6BC787BB16B1BC
Looking at the encoded version of the tokens is of course not useful to us, copy each JWT and head over to jwt.io and paste it there to see its contents
A JWT has three components to it and is split by dots [.
]
- A header containing it's algorithm and token type [the token type is used during validation]
- The payload containing all the user's claims
- A signature to verify that the contents of the token haven't been tampered with
The focus here is going to be on the payload of the access token
Here is the decoded payload from jwt.io
{
"nbf": 1680833230,
"exp": 1680836830,
"iss": "http://localhost:5005",
"aud": [
"SeelansTyresWebBff",
"http://localhost:5005/resources"
],
"client_id": "seelanstyresmvcfrontend",
"sub": "6c79df9e-68c9-4c7d-6958-08db370cc84c",
"auth_time": 1680833230,
"idp": "local",
"jti": "6B6618D082C39311DAD94CEDE8B6F24D",
"sid": "824BA3FFE6FD42CC66FC4A2FA6BA6931",
"iat": 1680833230,
"scope": [
"openid",
"profile",
"SeelansTyresWebBff.fullaccess",
"role",
"offline_access"
],
"amr": [
"pwd"
]
}
The following values are of importance here:
-
iss
: Issuer [http://localhost:5005
] | The creator of the token -
aud
: Audience [SeelansTyresWebBff
] | The intended recipient of the token -
sub
: Subject [6c79df9e-68c9-4c7d-6958-08db370cc84c
] | The id of the logged in user -
scope
: Scope [SeelansTyresWebBff.fullaccess
] | The granted permissions
In order to complete the explanation, some pieces of the other wiki pages need to be brought here
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.ClientId = builder.Configuration["ClientCredentials:ClientId"];
options.ClientSecret = builder.Configuration["ClientCredentials:ClientSecret"];
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = builder.Configuration["IdentityServer"];
options.RequireHttpsMetadata = false;
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("SeelansTyresWebBff.fullaccess");
options.Scope.Add("offline_access");
options.Scope.Add("role");
options.ClaimActions.MapUniqueJsonKey(ClaimTypes.Role, ClaimTypes.Role);
});
{
"Routes": [
{
"DownstreamPathTemplate": "/api/customers/{customerId}/addresses",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": "5011"
}
],
"UpstreamPathTemplate": "/addressservice/api/customers/{customerId}/addresses",
"UpstreamHttpMethod": [ "Get", "Post" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "SeelansTyresWebBffAuthenticationScheme"
},
"DelegatingHandlers": [
"AddressServiceDelegatingHandler"
]
}
]
}
The Mvc Frontend client requests the following scopes:
SeelansTyresWebBff.fullaccess
offline_access
role
The openid
and profile
scopes are added by default
Requesting SeelansTyresWebBff.fullaccess
adds SeelansTyresWebBff
as an audience and is reflected in the access token
{
"aud": [
"SeelansTyresWebBff",
"http://localhost:5005/resources" // ignore this
]
}
The requested scopes are reflected in the access token too
{
"scope": [
"openid",
"profile",
"SeelansTyresWebBff.fullaccess",
"role",
"offline_access"
]
}
Looking at Ocelot's configured AuthenticationOptions
, it has SeelansTyresWebBffAuthenticationScheme
as the AuthenticationProviderKey
which must match the configuration in Program.cs and does
var authenticationScheme = "SeelansTyresWebBffAuthenticationScheme";
Having it set like this does have the advantage of having many different types of authentication configured
When a request is received at the gateway, the access token is first validated before any other processing occurs
The raw token
eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9.eyJuYmYiOjE2ODA4MzMyNjYsImV4cCI6MTY4MDgzNjg2NiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDA1IiwiYXVkIjpbIkFkZHJlc3NTZXJ2aWNlIiwiaHR0cDovL2xvY2FsaG9zdDo1MDA1L3Jlc291cmNlcyJdLCJjbGllbnRfaWQiOiJzZWVsYW5zdHlyZXNtdmNiZmZ0b2Rvd25zdHJlYW0iLCJzdWIiOiI2Yzc5ZGY5ZS02OGM5LTRjN2QtNjk1OC0wOGRiMzcwY2M4NGMiLCJhdXRoX3RpbWUiOjE2ODA4MzMyNjYsImlkcCI6ImxvY2FsIiwianRpIjoiMkM2OTFGN0QzMjc5MTlFRjcwNDA2MjEyMTQzRUU4OEIiLCJpYXQiOjE2ODA4MzMyNjYsInNjb3BlIjpbIkFkZHJlc3NTZXJ2aWNlLmZ1bGxhY2Nlc3MiLCJvcGVuaWQiLCJwcm9maWxlIiwicm9sZSJdLCJhbXIiOlsiYWNjZXNzX3Rva2VuIiwicHdkIl19.KvwuvAeHHONNpzjFEe7z33bEdg2xqlvrgTKG3H9COlwZUjx7Ucf1iyYZkgdIq2xvbHmpzbaY5aFUQ-YPKhlEvU83UVBpFeieYAWqVYYfOSBZ4dYTJBxSdbIhlGTdzi2w4Y_ryNVKVC0JxJFx3j87F5_VImykTfaQeI_f2Y9XLV7sbP2WPVZ7uR3AYxgb7GIT-1Fs3OsHqTwXnyRAtWF6agiLYNKCrtkJwDp2c5DXbdIHis9odmpN-u3vYXuIgZaNtFPGW9Zq_pKWynLE796Tp5QrWmNWmZN2CJp4o9g4h5T7g5v-KIgagwcPfuNUvcwX_P1aw4CUfPTyOONW1ucQMw
The payload after being decoded
{
"nbf": 1680833266,
"exp": 1680836866,
"iss": "http://localhost:5005",
"aud": [
"AddressService",
"http://localhost:5005/resources"
],
"client_id": "seelanstyreswebbfftodownstream",
"sub": "6c79df9e-68c9-4c7d-6958-08db370cc84c",
"auth_time": 1680833266,
"idp": "local",
"jti": "2C691F7D327919EF70406212143EE88B",
"iat": 1680833266,
"scope": [
"AddressService.fullaccess",
"openid",
"profile",
"role"
],
"amr": [
"access_token",
"pwd"
]
}
Here we can see that AddressService.fullaccess
is added as a scope, and AddressService
is included as an audience
Also something to point out is the sub
claim [6c79df9e-68c9-4c7d-6958-08db370cc84c
], it's passed on from the original access token
If the user had a role, it'll also show up as one of the claims
This is the same example used to explain Ocelot
Scenario: GET all addresses for customer 00000000-0000-0000-0000-000000000000
Upstream: GET http://localhost:5050/addressservice/api/customers/00000000-0000-0000-0000-000000000000/addresses
Downstream: [Forwarded] GET http://localhost:5011/api/customers/00000000-0000-0000-0000-000000000000/addresses
sequenceDiagram
participant mvc as Mvc Frontend
participant webbff as Web Backend-For-Frontend
participant addressservice as Address Service
mvc ->> webbff: Makes GET request
webbff ->> webbff: Validates the access token
webbff ->> addressservice: Forwards GET request downstream
addressservice ->> addressservice: Validates the access token
addressservice -->> webbff: 200 OK
webbff -->> mvc: 200 OK
This diagram skips over the token exchange process as it doesn't contribute to where in the process access tokens are validated
- Health Checks UI
- Mvc Frontend
- Web Backend-For-Frontend
- Address Service
- Address Worker
- Identity Service
- Order Service
- Order Worker
- Tyres Service