« AWS SDK for .NET » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
(22 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 55 : | Ligne 61 : | ||
= [https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/sso-tutorial-app-only.html#sso-tutorial-app-only-code Example .NET applications] = | = [https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/sso-tutorial-app-only.html#sso-tutorial-app-only-code Example .NET applications] = | ||
<kode lang='cs' collapsed> | <kode lang='cs' collapsed> | ||
var ssoCreds = this.LoadSsoCredentials(" | var ssoCreds = this.LoadSsoCredentials("Profile1"); | ||
var token = new AmazonSecurityTokenServiceClient(ssoCreds); | var token = new AmazonSecurityTokenServiceClient(ssoCreds); | ||
Ligne 100 : | Ligne 106 : | ||
{ | { | ||
var s3Client = new AmazonS3Client(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(); | var listResponse = await s3Client.ListBucketsAsync(); | ||
return listResponse.Buckets.Select(x => x.BucketName).ToList(); | return listResponse.Buckets.Select(x => x.BucketName).ToList(); | ||
Ligne 109 : | Ligne 117 : | ||
For S3 buckets: {{boxx|AWSSDK.S3}} | For S3 buckets: {{boxx|AWSSDK.S3}} | ||
= [https:// | = [https://referbruv.com/blog/securing-aspnet-core-apis-with-jwt-bearer-using-aws-cognito/ Authentication with Cognito JWT Token] = | ||
<filebox fn='Program.cs'> | <filebox fn='Program.cs'> | ||
builder.Services.AddCognitoIdentity(); | builder.Services.AddCognitoIdentity(); | ||
builder.Services.AddAuthentication(options => | builder.Services.AddAuthentication(options => | ||
{ | { | ||
Ligne 125 : | 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 = | 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; | |||
}; | }; | ||
}); | |||
</filebox> | |||
Install the following nuget packages: {{boxx|Amazon.AspNetCore.Identity.Cognito}} | |||
[https://github.com/aws/aws-aspnet-cognito-identity-provider aws-aspnet-cognito-identity-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> | </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 } } |