While working with .net core I needed to create API. For this what I aimed to have was proper authentication. Therefore I decided to use JSON Web Token (JWT) authentication.
However I wanted to avoid creating any of this logic by myself or spending too much time on it. That’s why I decided to use AWS Cognito User Pools to provide me with user management and to generate JWT I need.
It took me some time to gather information how to wire it all together so I will try to outline the most important.
AWS setup
- Create user pool in AWS Cognito
- Get the newly created user pool ID and run the following command
curl https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/jwks.json > result.json
* if you want to you can also just navigate to the URL (https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/jwks.json ) . Just replace region and user pool ID with correct information.
- The information you receive will be used by us to validate the tokens given by AWS.
- Save the results for later use.
.Net core API project
- Create new .net core webapi project
dotnet new webapi
- Install additional packages
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer dotnet add package Microsoft.IdentityModel.Tokens dotnet add package Microsoft.AspNetCore.Identity
- add JWT authentication policy ( we will decorate our controllers with it )
services.AddAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser() .Build()); });
- Before making further modifications we will add 2 methods used which will be used to validate the signature and issuer ( this has potential to be made much better 🙂 )Key is the “n” value and Expo is the “e” value in the keys you got form the url in AWS setup
public RsaSecurityKey SigningKey(string Key, string Expo) { RSA rrr = RSA.Create(); rrr.ImportParameters( new RSAParameters() { Modulus = Base64UrlEncoder.DecodeBytes(Key), Exponent = Base64UrlEncoder.DecodeBytes(Expo) } ); return new RsaSecurityKey(rrr); } public TokenValidationParameters TokenValidationParameters(string issuer) { // Basic settings - signing key to validate with, audience and issuer. return new TokenValidationParameters { // Basic settings - signing key to validate with, IssuerSigningKey and issuer. IssuerSigningKey = this.SigningKey(<key-comes-here>,<expo-comes-here>), ValidIssuer = issuer, // when receiving a token, check that the signing key ValidateIssuerSigningKey = true, // When receiving a token, check that we've signed it. ValidateIssuer = true, // When receiving a token, check that it is still valid. ValidateLifetime = true, // Do not validate Audience on the "access" token since Cognito does not supply it but it is on the "id" ValidateAudience = false, // This defines the maximum allowable clock skew - i.e. provides a tolerance on the token expiry time // when validating the lifetime. As we're creating the tokens locally and validating them on the same // machines which should have synchronised time, this can be set to zero. Where external tokens are // used, some leeway here could be useful. ClockSkew = TimeSpan.FromMinutes(0) }; }
- Modify Configure method to enable JWT
app.UseJwtBearerAuthentication(new JwtBearerOptions() { TokenValidationParameters = this.TokenValidationParameters(<issuer-comes-here>) });
The issuer format has the following format : https://cognito-idp.<region>.amazonaws.com/<user-pool-id>
- Modify controller and enable the authentication by using the following decorator
[Authorize(Policy = "Bearer")]
Testing the solution
With the authentication enabled we get the following while requesting controller
> http http://localhost:5000/api/values HTTP/1.1 401 Unauthorized Content-Length: 0 Date: Sun, 30 Jul 2017 11:41:33 GMT Server: Kestrel WWW-Authenticate: Bearer
And if we pass the JWT 🙂
http --auth-type=jwt -v http://localhost:5000/api/values GET /api/values HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Authorization: Bearer ey..... Host: localhost:5000 User-Agent: HTTPie/0.9.9 HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Date: Sun, 30 Jul 2017 11:45:41 GMT Server: Kestrel Transfer-Encoding: chunked
Code
Full gist below 🙂