« AWS SDK for .NET » : différence entre les versions
Apparence
(37 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
[[Category:AWS]] | [[Category:AWS]] | ||
= [https://docs.aws.amazon.com/ | = Links = | ||
* [https:// | * [https://repost.aws/knowledge-center/decode-verify-cognito-json-token Decode and verify the signature of an Cognito JWT] | ||
* [https://aws.amazon.com/ | * [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] = | |||
This file contains the profiles. | |||
<filebox fn='∼/.aws/config' lang='ini'> | |||
[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 | |||
</filebox> | |||
<kode lang='ps'> | |||
aws sso login --profile Profile1 | |||
</kode> | |||
Define the {{boxx|AWS_PROFILE}} in an env var while starting the project. | |||
<filebox fn='Properties\launchSettings.json' collapsed> | |||
{ | |||
"profiles": { | |||
"MyProfile1": { | |||
"commandName": "Project", | |||
"launchBrowser": true, | |||
"launchUrl": "swagger", | |||
"environmentVariables": { | |||
"ASPNETCORE_ENVIRONMENT": "Development", | |||
"AWS_PROFILE": "Profile1" | |||
}, | |||
"applicationUrl": "https://localhost:5001;http://localhost:5000" | |||
} | |||
} | |||
</filebox> | |||
= [https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html#file-format-creds Credentials] = | = [https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html#file-format-creds Credentials] = | ||
This file contains credentials linked to profiles. | |||
<filebox fn='∼/.aws/credentials' lang='ini'> | <filebox fn='∼/.aws/credentials' lang='ini'> | ||
[default] | [default] | ||
aws_access_key_id=... | aws_access_key_id = ... | ||
aws_secret_access_key=... | aws_secret_access_key = ... | ||
aws_session_token=... | aws_session_token = ... | ||
[ | [Profile1] | ||
key=value | key = value | ||
</filebox> | </filebox> | ||
= Cognito = | = [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> | |||
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(); | |||
} | |||
</kode> | |||
Install the following nuget packages: {{boxx|AWSSDK.Core}} {{boxx|AWSSDK.SecurityToken}} {{boxx|AWSSDK.SSO}} {{boxx|AWSSDK.SSOOIDC}}<br> | |||
For IAM users: {{boxx|AWSSDK.IdentityManagement}}<br> | |||
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] = | |||
<filebox fn='Program.cs'> | <filebox fn='Program.cs'> | ||
builder.Services.AddCognitoIdentity(); | builder.Services.AddCognitoIdentity(); | ||
builder.Services.AddAuthentication(options => | builder.Services.AddAuthentication(options => | ||
{ | { | ||
Ligne 27 : | 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
}
}
|