Security Token Service (STS)
Introduction
The Security Token Service (STS) helps with providing temporary S3 access to users authenticated via a trusted Identity Provider (IdP).
How it works
- We create a technical user on the SCS3 project instance account.
- The technical user is used to create temporary credentials for IdP users.
- We register the IdP as an OIDC provider to the account.
- We create a role the technical user can assume.
- An IdP user logs in to the IdP service and receives a JWT token.
- We use the technical user with the IdP user's JWT token to create a temporary session.
- The IdP user can now use the temporary credentials to access SCS3.
Create a technical user
To use STS, you must first create a service user for your application/use case.
In our examples we use the AWS CLI tool, but any other compatible client or SDK can work as well. To install and set up AWS CLI, you can follow this guide: user authentication.
Caution
In the following guide, when we refer to users plainly, we mean the IdP users and not the users of the S3 account.
$ aws iam create-user --user-name my-sts-bot
{
"User": {
"Path": "/",
"UserName": "my-sts-bot",
"UserId": "716f95d3-978d-4b8f-b2aa-5dac9a014a8d",
"Arn": "arn:aws:iam::RGW05119666406654860:user/my-sts-bot",
"CreateDate": "2026-09-24T09:21:47.586987+00:00"
}
}
$ aws iam create-access-key --user-name my-sts-bot
{
"AccessKey": {
"UserName": "my-sts-bot",
"AccessKeyId": "3EW92VOIRPPZA2DPWDIF",
"Status": "Active",
"SecretAccessKey": "4LSYquDBGAxSVMbonNqA9tpoHBGCQlKzRx414upQ",
"CreateDate": "2026-09-24T09:22:14.303384+00:00"
}
}
With this service user, we will be able to create temporary credentials with custom policies.
Add your own authentication provider
Next, we will add our OIDC provider; in our example, this will be a Keycloak instance.
We must provide the client ID of our IdP and the thumbprint (SHA-1 hash) that is used to sign the JWT tokens. In AWS it is optional, because it computes the first key's thumbprint, but in SCS3 it is always required.
To find the list of keys used to sign JWT tokens, you can follow your IdP's discovery URL
https://<idp url>/.well-known/openid-configuration and then follow the jwks_uri URL.
You can search on the web, based on the key's algorithm, on how to compute its SHA-1 thumbprint.
You can use up to 5 hashes in the thumbprint list for a single OIDC provider.
aws iam create-open-id-connect-provider --url http://<keycloak_ip>/realms/master --client-id-list my-web-app --thumbprint-list 6A0A7DE592AB2F03546BF2844043472FFEA1FF57
{
"OpenIDConnectProviderArn": "arn:aws:iam::RGW05119666406654860:oidc-provider/<keycloak_hostname>/realms/master"
}
By running the above command, we have managed to add our own OIDC provider for provisioning temporary credentials.
If any changes happen, it is easier to just remove and then add again the provider with the new arguments.
The OIDC provider can have any valid URL format, e.g. https://<ip>:<port>/<path> or https://<hostname>, the OIDC ID
then becomes the URL value with the protocol part stripped.
Create a role with custom policies
Basic Setup
To allow users authenticated via an OIDC provider to interact with S3, we would need our technical user to assume a role with some constraints.
A role is part of the aws sts api where users can act as other entities with different permissions for a limited
amount of time.
In our case, we would want the service/bot user we have created to assume a short-lived role that has the special
permissions we want for our use case.
In our example, let's say we want users authenticated in our webapp to access assets based on the organisation they are in.
Once a user is authenticated, they will have a JWT token looking like this:
{
"sub": "a_user",
"aud": "my-web-app",
"azp": "my-web-app",
"jti": "ZYUCeRMQVtqHypVPWAN3VB",
"iss": "http://<keycloak_ip>/realms/master",
"iat": 1790253873,
"exp": 1790257473,
"auth_time": 1790253873,
}
SCS3 requires the token to have at least the sub (subject) and aud (audience) claims to allow a JWT token to be
passed to the sts assume-role-with-web-identity API call.
We must now create a role that allows an authenticated user of my-web-app OIDC client to access the account's
resources.
A role requires at least two policies: one policy to allow someone to take on the role and a policy that allows the user acting on that role to perform certain actions.
Here is an example of a role assumption policy that allows users authenticated to my-web-app to assume the below role.
// assume-policy.json:
// <idp-id>: e.g. keycloak.example.com/realms/master
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": [
"arn:aws:iam::RGW05119666406654860:oidc-provider/<idp-id>"
]
},
"Action": [
"sts:AssumeRoleWithWebIdentity"
],
"Condition": {
"StringEquals": {
"<idp-id>:azp": "my-web-app"
}
}
}
]
}
Using the ID of the OIDC provider, we can do all sorts of conditions to check whether a user of our service is allowed to assume our role.
For more info on policy conditions, please visit the official AWS documentations on policy conditions.
Once we have created an assume role policy to our liking, we can now create the role:
$ aws iam create-role --role-name my-web-app-role --assume-role-policy-document file://assume-policy.json
{
"Role": {
"Path": "/",
"RoleName": "my-web-app-role",
"RoleId": "4ffe5c57-0a77-459a-a329-feddcd10cdce",
"Arn": "arn:aws:iam::RGW05119666406654860:role/my-web-app-role",
"CreateDate": "2026-09-24T13:05:21.756000+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": [
"arn:aws:iam::RGW05119666406654860:oidc-provider/<idp-id>"
]
},
"Action": [
"sts:AssumeRoleWithWebIdentity"
],
"Condition": {
"StringEquals": {
"<idp-id>:azp": "my-web-app"
}
}
}
]
},
"Description": "",
"MaxSessionDuration": 3600
}
}
We can now attach a managed policy to the role or put a custom one. For example, we can add a readonly policy so users who assume this role can read the account's buckets and objects.
$ aws iam attach-role-policy --role-name my-web-app-role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# OR
$ aws iam put-role-policy --role-name my-web-app-role \
--policy-name policy1 --policy-document file://my-custom-policy.json
For more information about policies, please follow this guide: S3 Policies
Advanced Setup
For more controlled and fine-grained permissions, we must pass more information to the session that will be created when the user assumes the role.
Information from the JWT token will be lost once the user obtains their temporary S3 credentials.
For example, with the previous setup, if we want users from different departments that are defined in our own IdP to assume a role and have access to their own buckets each, we would have to create a role per department and add a hardcoded policy for each role to access a certain set of buckets, e.g., a role for accessing IT-related S3 buckets or a different role for legal-related S3 buckets, etc.
Session tags can help mitigate this issue. To allow this feature, we must first configure our IdP to create JWT tokens of the following form:
{
"sub": "a_user",
"aud": "my-web-app",
"azp": "my-web-app",
"jti": "ZYUCeRMQVtqHypVPWAN3VB",
"iss": "http://<keycloak_ip>/realms/master",
"iat": 1790253873,
"exp": 1790257473,
"auth_time": 1790253873,
"https://aws.amazon.com/tags": {
"principal_tags": {
"Department": "IT",
"Organization": "SWITCH",
"Team": "IT Support"
}
}
}
The principal tags will carry over to the session, and we can use them to create better policies.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRoleWithWebIdentity",
"sts:TagSession"
],
"Principal": {
"Federated": [
"arn:aws:iam:::oidc-provider/<idp-id>"
]
},
"Condition": {
"StringEquals": {
"<idp-id>:aud": "my-web-app",
"aws:RequestTag/Organization": "SWITCH"
}
}
}
]
}
Using aws:RequestTag/<key> we can check for the tags passed to the JWT token that the IdP has generated.
In the above example we allow members of the "SWITCH" organisation that have authenticated to "my-web-app" to assume the
above role.
Then when a user assumes the role, the JWT token tags are now available as aws:PrincipalTag.
Now, we can put a policy to the above role that allows all S3 actions on certain buckets.
{
"Version":"2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::${aws:PrincipalTag/Department}-bucket/",
"arn:aws:s3:::${aws:PrincipalTag/Department}-bucket/*"
]
}
]
}
With this policy added, your IdP users can access only their own buckets, so a user that belongs to the IT department
can only access the IT-bucket of your SCS3 account.
Assume role and get temporary credentials
Once everything above is set up, a user can receive the credentials by providing their own web token and session name.
To assume the role with JWT, you must provide the:
- Role to assume
- The session name
- Must be different for different IdP users
- The session duration
- 900 seconds is the minimum value
- The encrypted JWT
$ aws sts assume-role-with-web-identity \
--role-arn "arn:aws:iam::RGW05119666406654860:role/my-web-app-role" \
--role-session-name sts-test \
--duration-seconds 900 \
--web-identity-token "<JWT value>"
{
"Credentials": {
"AccessKeyId": "tgyQqZqRaNmLeFWBzuYv",
"SecretAccessKey": "RA0IWJV6SRRV...",
"SessionToken": "wJ3Eer/YXMpy64HtOkeCUS...",
"Expiration": "2026-09-24T14:42:21.319138+00:00"
},
"SubjectFromWebIdentityToken": "8cc0a703-6c30-4945-85bf-fd557f0e2efc",
"AssumedRoleUser": {
"Arn": "arn:aws:sts::RGW05119666406654860:assumed-role/my-web-app-role/sts-test"
},
"PackedPolicySize": 0,
"Provider": "http://<keycloak-ip>/realms/master",
"Audience": "account"
}
You can then use the aws-cli by configuring your temp profile like the following and using the aws-cli with
--profile temp-user.
# ~/.aws/credentials:
[temp-user]
aws_access_key_id = tgyQqZqRaNmLeFWBzuYv
aws_secret_access_key = RA0IWJV6SRRV...
aws_session_token = wJ3Eer/YXMpy64HtOkeCUS...
The IdP user can now access your account's SCS3 resources based on the policies you have set for the role.
Limitations
- Up to 5 thumbprints can be used when adding an OIDC provider
- JWT token tags have the following limits:
- Up to 50 principal token keys
- 128-byte token key size
- 256-byte token value size
- Keys and their values can't start with
aws:
- SCS3 supports the following JWT hash algorithms:
- RSA: RS256/RS384/RS512
- ECDSA: ES256/ES384/ES512
- RSA-PSS: PS256/PS384/PS512
- EdDSA and HMAC (HS) algorithms are not supported
Comments
To use the STS service, quite a few steps must be followed to allow someone to access the S3 endpoint. In general, this procedure is programmed behind an application, so the users will only login to a web page and then receive their temporary S3 credentials.
For long-term access to S3 by an application, it might make more sense to create a separate service user in the SCS3 account with normal credentials that are rotated frequently instead of relying on STS.
STS is more suited when the users of an S3 account are fluctuating quite a lot, and that creates friction for an S3 account admin. STS will automate the process of "creating" a user, creating access keys, then "deleting" the user and the access keys, without having an admin manually taking care of everything (users/policies/keys).