« AWS SDK for .NET » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
(16 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
[[Category:AWS]] | [[Category:AWS]] | ||
= Links = | |||
* [https://repost.aws/knowledge-center/decode-verify-cognito-json-token Decode and verify the signature of an Cognito JWT] | |||
* [https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html#user-pool-lambda-pre-token-generation-accesstoken Customizing the access token] | |||
* [https://aws.amazon.com/blogs/compute/evaluating-access-control-methods-to-secure-amazon-api-gateway-apis/ Access control methods to secure Amazon API Gateway APIs] | |||
* [[Lambda]] | |||
= [https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html#file-format-config Config] = | = [https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html#file-format-config Config] = | ||
This file contains the profiles. | This file contains the profiles. | ||
Ligne 110 : | Ligne 116 : | ||
For IAM users: {{boxx|AWSSDK.IdentityManagement}}<br> | For IAM users: {{boxx|AWSSDK.IdentityManagement}}<br> | ||
For S3 buckets: {{boxx|AWSSDK.S3}} | For S3 buckets: {{boxx|AWSSDK.S3}} | ||
= [https://referbruv.com/blog/securing-aspnet-core-apis-with-jwt-bearer-using-aws-cognito/ Authentication with Cognito JWT Token] = | = [https://referbruv.com/blog/securing-aspnet-core-apis-with-jwt-bearer-using-aws-cognito/ Authentication with Cognito JWT Token] = | ||
Ligne 128 : | Ligne 130 : | ||
options.Authority = builder.Configuration["AWSCognito:Authority"]; | options.Authority = builder.Configuration["AWSCognito:Authority"]; | ||
options.Audience = builder.Configuration["AWSCognito:UserPoolClientId"]; | options.Audience = builder.Configuration["AWSCognito:UserPoolClientId"]; | ||
options.TokenValidationParameters.AudienceValidator = (audiences, securityToken, validationParameters) => | options.TokenValidationParameters.AudienceValidator = (audiences, securityToken, validationParameters) => | ||
{ | { | ||
// Cognito tokens doesn't have "aud" claim. Instead the audience is set in "client_id" | // This is necessary because Cognito access tokens doesn't have "aud" claim. | ||
var | // Instead the audience is set in "client_id" | ||
var jwt = (JsonWebToken)securityToken; | |||
var audience = jwt.Claims.FirstOrDefault(x => x.Type == "client_id" || x.Type == "aud"); | |||
if (audience is null) | |||
return false; | return false; | ||
return validationParameters.ValidAudience | |||
return validationParameters.ValidAudience == audience.Value; | |||
}; | }; | ||
}); | }); | ||
Ligne 149 : | Ligne 149 : | ||
{{warn | Cognito is not a fully OIDC-compliant provider}} | {{warn | Cognito is not a fully OIDC-compliant provider}} | ||
== Validate Issuer Signing Key == | |||
Useful id an ID token is used instead of an Access token. | |||
<filebox fn='Program.cs'> | |||
var signingKeys = await GetSigningKeysAsync(); | |||
builder.Services.AddJwtBearer(options => | |||
{ | |||
options.TokenValidationParameters = new TokenValidationParameters | |||
{ | |||
ValidateIssuerSigningKey = true, // needed if an ID token is used | |||
IssuerSigningKeys = signingKeys | |||
}; | |||
}); | |||
async Task<IList<JsonWebKey>> GetSigningKeysAsync() | |||
{ | |||
var httpClient = new HttpClient(); | |||
var response = await httpClient.GetAsync($"{builder.Configuration["AWSCognito:Authority"]}/.well-known/jwks.json"); | |||
var keySet = await response.Content.ReadAsAsync<JsonWebKeySet>(); | |||
return keySet.Keys; | |||
} | |||
</filebox> | |||
* [https://medium.com/@marcio_30193/jwt-machine-to-machine-usando-aws-cognito-and-c-b1fab0524712 JWT “Machine to Machine” usando AWS Cognito and C#] | |||
* [https://stackoverflow.com/questions/66892866/issuersigningkeyresolver-call-async-method IssuerSigningKeyResolver call async method] | |||
= Get user info from Cognito in an ASP.NET web API = | |||
Usually a web API is called with an Access Token which doesn't contain information regarding the user but instead authorizations for actions. | |||
{{warn | The AccessToken needs to have the scope {{boxx|aws.cognito.signin.user.admin}} or no scope to be allowed to call {{boxx|cognitoService.GetUserAsync}}}} | |||
<filebox fn='Program.cs' collapsed> | |||
builder.Services.AddHttpContextAccessor() | |||
.AddAWSService<IAmazonCognitoIdentityProvider>(); | |||
</filebox> | |||
<filebox fn='ApplicationUserProvider.cs'> | |||
private readonly IHttpContextAccessor httpContextAccessor; | |||
private readonly IAmazonCognitoIdentityProvider cognitoService; | |||
public ApplicationUserProvider(IHttpContextAccessor httpContextAccessor, IAmazonCognitoIdentityProvider cognitoService) | |||
{ | |||
this.httpContextAccessor = httpContextAccessor; | |||
this.cognitoService = cognitoService; | |||
} | |||
public async Task GetUserInfo() | |||
{ | |||
var accessToken = await httpContextAccessor.HttpContext!.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); // "access_token" | |||
if (accessToken is not null) | |||
{ | |||
try | |||
{ | |||
var userResponse = await cognitoService.GetUserAsync(new GetUserRequest | |||
{ | |||
AccessToken = accessToken | |||
}); | |||
var userId = userResponse.UserAttributes.Find(x => x.Name == "sub")?.Value; | |||
var userName = userResponse.UserAttributes.Find(x => x.Name == "name")?.Value; | |||
var userEmail = userResponse.UserAttributes.Find(x => x.Name == "email")?.Value; | |||
} | |||
catch (NotAuthorizedException noe) { } // Access Token does not have required scopes | |||
} | |||
} | |||
</filebox> |
Dernière version du 10 juin 2024 à 15:59
Links
- Decode and verify the signature of an Cognito JWT
- Customizing the access token
- Access control methods to secure Amazon API Gateway APIs
- Lambda
Config
This file contains the profiles.
∼/.aws/config |
[default] region = eu-central-1 [profile Profile1] sso_start_url = https://my-sso-portal.awsapps.com/start sso_region = us-west-1 sso_account_id = 111122223333 sso_role_name = SampleRole region = eu-central-1 output = yaml-stream services = local-dynamodb [services local-dynamodb] dynamodb = endpoint_url = http://localhost:8000 |
aws sso login --profile Profile1 |
Define the AWS_PROFILE in an env var while starting the project.
Properties\launchSettings.json |
{ "profiles": { "MyProfile1": { "commandName": "Project", "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "AWS_PROFILE": "Profile1" }, "applicationUrl": "https://localhost:5001;http://localhost:5000" } } |
Credentials
This file contains credentials linked to profiles.
∼/.aws/credentials |
[default] aws_access_key_id = ... aws_secret_access_key = ... aws_session_token = ... [Profile1] key = value |
Example .NET applications
var ssoCreds = this.LoadSsoCredentials("Profile1"); var token = new AmazonSecurityTokenServiceClient(ssoCreds); var caller = await token.GetCallerIdentityAsync(new GetCallerIdentityRequest()); this.userId = caller?.UserId.Substring(caller.UserId.IndexOf(":") + 1) ?? string.Empty; var userNames = await this.GetIamUserNamesAsync(ssoCreds); var bucketNames = await this.GetS3BucketNames(ssoCreds); private AWSCredentials LoadSsoCredentials(string profile) { var chain = new CredentialProfileStoreChain(); if (!chain.TryGetAWSCredentials(profile, out var credentials)) { errors.Add($"Failed to find the {profile} profile"); } // set ClientName and launch a browser window that prompts the SSO user to complete an SSO login // if the session doesn't already have a valid SSO token. if (credentials is SSOAWSCredentials ssoCredentials) { ssoCredentials.Options.ClientName = "Example-SSO-App"; ssoCredentials.Options.SsoVerificationCallback = args => { Process.Start(new ProcessStartInfo { FileName = args.VerificationUriComplete, UseShellExecute = true }); }; } return credentials; } private async Task<IReadOnlyCollection<string>> GetIamUserNamesAsync(AWSCredentials ssoCreds) { var iamClient = new AmazonIdentityManagementServiceClient(ssoCreds); var listResponse = await iamClient.ListUsersAsync(); return listResponse.Users.Select(x => x.UserName).ToList(); } private async Task<IReadOnlyCollection<string>> GetS3BucketNames(AWSCredentials ssoCreds) { var s3Client = new AmazonS3Client(ssoCreds); // Amazon.Runtime.AmazonClientException: 'No RegionEndpoint or ServiceURL configured // define a default profile in config with a region var listResponse = await s3Client.ListBucketsAsync(); return listResponse.Buckets.Select(x => x.BucketName).ToList(); } |
Install the following nuget packages: AWSSDK.Core AWSSDK.SecurityToken AWSSDK.SSO AWSSDK.SSOOIDC
For IAM users: AWSSDK.IdentityManagement
For S3 buckets: AWSSDK.S3
Authentication with Cognito JWT Token
Program.cs |
builder.Services.AddCognitoIdentity(); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.Authority = builder.Configuration["AWSCognito:Authority"]; options.Audience = builder.Configuration["AWSCognito:UserPoolClientId"]; options.TokenValidationParameters.AudienceValidator = (audiences, securityToken, validationParameters) => { // This is necessary because Cognito access tokens doesn't have "aud" claim. // Instead the audience is set in "client_id" var jwt = (JsonWebToken)securityToken; var audience = jwt.Claims.FirstOrDefault(x => x.Type == "client_id" || x.Type == "aud"); if (audience is null) return false; return validationParameters.ValidAudience == audience.Value; }; }); |
Install the following nuget packages: Amazon.AspNetCore.Identity.Cognito
aws-aspnet-cognito-identity-provider
Cognito is not a fully OIDC-compliant provider |
Validate Issuer Signing Key
Useful id an ID token is used instead of an Access token.
Program.cs |
var signingKeys = await GetSigningKeysAsync(); builder.Services.AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, // needed if an ID token is used IssuerSigningKeys = signingKeys }; }); async Task<IList<JsonWebKey>> GetSigningKeysAsync() { var httpClient = new HttpClient(); var response = await httpClient.GetAsync($"{builder.Configuration["AWSCognito:Authority"]}/.well-known/jwks.json"); var keySet = await response.Content.ReadAsAsync<JsonWebKeySet>(); return keySet.Keys; } |
Get user info from Cognito in an ASP.NET web API
Usually a web API is called with an Access Token which doesn't contain information regarding the user but instead authorizations for actions.
The AccessToken needs to have the scope aws.cognito.signin.user.admin or no scope to be allowed to call cognitoService.GetUserAsync |
Program.cs |
builder.Services.AddHttpContextAccessor() .AddAWSService<IAmazonCognitoIdentityProvider>(); |
ApplicationUserProvider.cs |
private readonly IHttpContextAccessor httpContextAccessor; private readonly IAmazonCognitoIdentityProvider cognitoService; public ApplicationUserProvider(IHttpContextAccessor httpContextAccessor, IAmazonCognitoIdentityProvider cognitoService) { this.httpContextAccessor = httpContextAccessor; this.cognitoService = cognitoService; } public async Task GetUserInfo() { var accessToken = await httpContextAccessor.HttpContext!.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); // "access_token" if (accessToken is not null) { try { var userResponse = await cognitoService.GetUserAsync(new GetUserRequest { AccessToken = accessToken }); var userId = userResponse.UserAttributes.Find(x => x.Name == "sub")?.Value; var userName = userResponse.UserAttributes.Find(x => x.Name == "name")?.Value; var userEmail = userResponse.UserAttributes.Find(x => x.Name == "email")?.Value; } catch (NotAuthorizedException noe) { } // Access Token does not have required scopes } } |