Skip to content

Instantly share code, notes, and snippets.

@guitarrapc
Last active April 14, 2025 16:50
Show Gist options
  • Save guitarrapc/a87f769a9dcb5d3fdbeab9954bcb20d4 to your computer and use it in GitHub Desktop.
Save guitarrapc/a87f769a9dcb5d3fdbeab9954bcb20d4 to your computer and use it in GitHub Desktop.
Reuse AWS SSO Credential in LinqPad or Any C# code. Make sure run `aws sso login` before run code. based on https://github.com/aws/aws-sdk-net/issues/1676

Update History

  • Apr 12, 2025: Add AWS official way to retrieve credentials by 2 way, SSO and SSO sign-in. Now handmade way is deprecated
  • Apr 10, 2025: Add AWS SDK v2 sso format support. (Include both ~/.aws/config sso-session and SSO cache file name support.)
  • Dec 3, 2021: Initial. AWS SDK v1 sso format support.

Getting started (GetSSOProfileCredentials)

For App usage.

  1. Add NuGet Package
dotnet add package AWSSDK.Core
dotnet add package AWSSDK.SSO
dotnet add package AWSSDK.SSOIDC

# and any other AWSSDK package for app. Example uses S3
dotnet add package AWSSDK.S3
  1. Rename Profile to your AWS Profile name.
  2. Run aws sso login --profile YOUR_PROFILE_NAMe to retrieve SSO session and cache.
  3. Run code. Now you can retrieve s3 buckets by SSO session's permission.

Getting started (GetSSOProfileCredentialsAndLogin)

For CLI usage.

  1. Add NuGet Package
dotnet add package AWSSDK.Core
dotnet add package AWSSDK.SSO
dotnet add package AWSSDK.SSOIDC

# and any other AWSSDK package for app. Example uses S3
dotnet add package AWSSDK.S3
  1. Rename Profile to your AWS Profile name.
  2. Run code. Browser will open, sign-in and credentials will use.

Getting started (HandMaid_GetSSOProfileCredentials)

  1. Add NuGet Package
dotnet add package AWSSDK.Core
dotnet add package AWSSDK.SecurityToken
dotnet add package AWSSDK.SSO

# and any other AWSSDK package for app. Example uses S3
dotnet add package AWSSDK.S3
  1. Rename Profile to your AWS Profile name.
  2. Run aws sso login --profile YOUR_PROFILE_NAMe to retrieve SSO session and cache.
  3. Run code. Now you can retrieve s3 buckets by SSO session's permission.
var profile = "YOUR_PROFILE_NAME";
var credentials = LoadSsoCredentials(profile);
// any operation you want to do with sso credentials.
var s3client = new AmazonS3Client(credentials, RegionEndpoint.APNortheast1);
var buckets = await s3client.ListBucketsAsync();
buckets.Dump();
static AWSCredentials LoadSsoCredentials(string profileName)
{
var chain = new CredentialProfileStoreChain();
if (!chain.TryGetAWSCredentials(profileName, out var credentials))
throw new Exception("Failed to find the my-sso-profile profile");
return credentials;
}
// aws recommended
var profile = "YOUR_PROFILE_NAME";
var credentials = LoadSsoCredentials(profile);
// any operation you want to do with sso credentials.
var s3client = new AmazonS3Client(credentials, RegionEndpoint.APNortheast1);
var buckets = await s3client.ListBucketsAsync();
buckets.Dump();
static AWSCredentials LoadSsoCredentials(string profileName)
{
var chain = new CredentialProfileStoreChain();
if (!chain.TryGetAWSCredentials(profileName, out var credentials))
throw new Exception("Failed to find the my-sso-profile profile");
var ssoCredentials = credentials as SSOAWSCredentials;
ssoCredentials.Options.ClientName = "LinqPad";
ssoCredentials.Options.SsoVerificationCallback = args =>
{
// Launch a browser window that prompts the SSO user to complete an SSO sign-in.
// This method is only invoked if the session doesn't already have a valid SSO token.
// NOTE: Process.Start might not support launching a browser on macOS or Linux. If not,
// use an appropriate mechanism on those systems instead.
Process.Start(new ProcessStartInfo
{
FileName = args.VerificationUriComplete,
UseShellExecute = true
});
};
return ssoCredentials;
}
var profile = "YOUR_PROFILE_NAME";
var credentials = await GetSSOProfileCredentials(profile);
// any operation you want to do with sso credentials.
var s3client = new AmazonS3Client(credentials, RegionEndpoint.APNortheast1);
var buckets = await s3client.ListBucketsAsync();
foreach (var bucket in buckets.Buckets)
{
Console.WriteLine(bucket.BucketName);
}
/// <summary>
/// Get AccessKey from AWS SSO cached file.
/// </summary>
static async Task<AWSCredentials> GetSSOProfileCredentials(string profileName)
{
// ${HOME}/.aws/config
var configFilePath = Path.Combine(SharedCredentialsFile.DefaultDirectory, "config");
// ${HOME}/.aws/sso/cache
var ssoFolderPath = Path.Combine(SharedCredentialsFile.DefaultDirectory, "sso", "cache");
var (accountId, roleName, ssoCacheSeed) = GetSsoProfileValues(configFilePath, profileName);
var cacheFileName = GetSha1(ssoCacheSeed) + ".json";
var fullCacheFilePath = Path.Combine(ssoFolderPath, cacheFileName);
if (!File.Exists(fullCacheFilePath))
throw new FileNotFoundException($"aws sso cache file {fullCacheFilePath} not found, please confirm you have already logged in with 'aws sso login --profile {profileName}'");
var cacheObject = JsonSerializer.Deserialize<AwsSsoCacheObject>(File.ReadAllText(fullCacheFilePath));
if (cacheObject is null)
throw new ArgumentNullException(nameof(cacheObject));
if (cacheObject.ExpiresAt < DateTime.UtcNow)
throw new InvalidDataException("Obtained expiresAt is past date.");
using var ssoClient = new AmazonSSOClient(
new AnonymousAWSCredentials(),
new AmazonSSOConfig { RegionEndpoint = RegionEndpoint.GetBySystemName(cacheObject.Region) });
var getRoleCredentialsResponse = await ssoClient.GetRoleCredentialsAsync(new GetRoleCredentialsRequest
{
AccessToken = cacheObject.AccessToken,
AccountId = accountId,
RoleName = roleName
});
var sessionCredential = new SessionAWSCredentials(
getRoleCredentialsResponse.RoleCredentials.AccessKeyId,
getRoleCredentialsResponse.RoleCredentials.SecretAccessKey,
getRoleCredentialsResponse.RoleCredentials.SessionToken);
// You can omit on local run. For Role credential fallback. Fargate/Lambda/EC2 Instance.
FallbackCredentialsFactory.CredentialsGenerators.Insert(0, () => sessionCredential);
return sessionCredential;
static string GetSha1(string value)
{
using var sha = SHA1.Create();
var hashSpan = sha.ComputeHash(Encoding.UTF8.GetBytes(value)).AsSpan();
return Convert.ToHexString(hashSpan).ToLowerInvariant();
}
static (string AccountId, string RoleName, string SsoCacheSeed) GetSsoProfileValues(string configFilePath, string profileName)
{
var profileFile = new ProfileIniFile(configFilePath, false);
if (!profileFile.TryGetSection(profileName, false, false, out var profileProperties, out _))
throw new ArgumentNullException($"profile '{profileProperties}' not found in {configFilePath}.");
var accountId = profileProperties["sso_account_id"];
var roleName = profileProperties["sso_role_name"];
// Get SSO cache file seed.
// * legacy format use sso_start_url to generate sso cache file
// * new format use sso_session name to generate sso cache file (see: https://github.com/aws/aws-sdk-go-v2/blob/24f7e3aaaf4a6e94d2fc05a7ef0fac4bb80b7e01/config/resolve_credentials.go#L211)
if (!profileProperties.TryGetValue("sso_start_url", out var ssoCacheSeed))
{
// both legacy and new format is missing
if (!profileProperties.TryGetValue("sso_session", out var ssoSesssion))
throw new ArgumentNullException($"Both sso_start_url and sso_session missing in profile {profileName}");
if (profileFile.TryGetSection(ssoSesssion, true, false, out var ssoSessionProperties, out _))
{
ssoCacheSeed = ssoSesssion; // new format
}
}
return (accountId, roleName, ssoCacheSeed);
}
}
internal class AwsSsoCacheObject
{
[JsonPropertyName("startUrl")]
public required string StartUrl { get; init; }
[JsonPropertyName("region")]
public required string Region { get; init; }
[JsonPropertyName("accessToken")]
public required string AccessToken { get; init; }
[JsonPropertyName("expiresAt")]
public required DateTime ExpiresAt { get; init; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment