The pool's own users can now sign in through /oauth2/authorize the way apps expect Cognito to work: without identity_provider (or with COGNITO) the browser lands on a hosted login page, and a correct password redirects to the registered callback with a one-time code. - GET/POST /cognito-idp/login (and /login on custom domains), with a CSRF cookie and the same password rules and Pre/Post Authentication triggers as USER_PASSWORD_AUTH; wrong passwords, unknown and unconfirmed users get the same answer until the password is right - a one-hour browser session, so the next authorize skips the form unless it sends prompt=login, and /logout with LogoutURLs validation - PKCE S256 on both hosted and federated codes, and nonce in the ID token - refresh_token grant on /oauth2/token - codes now fire Pre Token Generation with TokenGeneration_HostedAuth instead of skipping the trigger
28 KiB
Cognito
Protocol: JSON 1.1 (X-Amz-Target: AWSCognitoIdentityProviderService.*)
Endpoint: POST http://localhost:4566/
Floci serves pool-specific discovery and JWKS endpoints, plus a relaxed OAuth token endpoint, so local clients can mint and validate Cognito-like access tokens against RS256 signing keys.
CreateUserPool supports overiding several values using user-pool tags only at creation time:
floci:override-id, to pin the resultingUserPool.Id. Because a pinned id is caller-chosen it can be reused, which AWS never does.DeleteUserPooltherefore deletes everything the pool owns (users, groups, app clients, resource servers, revoked token records and outstanding verification codes) so a pool recreated on the same id starts empty rather than inheriting the deleted pool's password hashes and client secrets.floci:override-cognito-client-id- set to
use-nameto use the client name as client ID. - set to
append-to-name:-somestringto append a string to the client name to be used as client ID. - set to
prepend-to-name:somestring-to prepend a string to the client name to be used as client ID.
- set to
floci:override-cognito-client-secret, to set the secret for all clients created in this userpool.
Floci strips reserved floci:* tags from stored and returned UserPoolTags on both create and update paths, so the tag namespace acts as an input-only control channel and is never persisted as user-visible metadata.
Standalone TagResource rejects reserved floci:* keys. ListTagsForResource and UntagResource operate on the persisted user-pool tag map.
An action given a user pool ID that does not resolve returns ResourceNotFoundException with the live service's wording, User pool <poolId> does not exist., so tooling that matches Cognito error text behaves the same way locally.
Supported Actions
User Pools
| Action | Description |
|---|---|
| CreateUserPool | Creates a local user pool, applying supported floci:* creation-time overrides from tags. |
| DescribeUserPool | Returns the stored user pool configuration. |
| ListUserPools | Lists local user pools visible in the request region. |
| UpdateUserPool | Updates mutable user pool settings and persisted user-pool tags. |
| DeleteUserPool | Deletes a local user pool and everything it owns: users, groups, app clients, resource servers, identity providers, revoked tokens and verification codes. Refused with InvalidParameterException while DeletionProtection is ACTIVE (switch it to INACTIVE with UpdateUserPool first, as on AWS) or while a domain is still configured. |
| GetUserPoolMfaConfig | Returns the pool's MFA mode and, once configured, its software-token setting. |
| SetUserPoolMfaConfig | Sets MfaConfiguration (OFF/ON/OPTIONAL) and SoftwareTokenMfaConfiguration. An absent MfaConfiguration means OFF, and turning MFA off drops the factor configuration with it. Validation follows the live service: OFF alongside a software-token, email or SMS factor is rejected, and ON/OPTIONAL with none of those three is rejected, in both cases on the member being present, not on its Enabled value. WebAuthnConfiguration sits outside both rules, as it does in AWS. SMS, email and WebAuthn configurations are validated and not stored: Floci cannot deliver those factors, so keeping the config would imply a capability it does not have. |
User Pool Tags
| Action | Description |
|---|---|
| TagResource | Adds user-visible tags to a user pool and rejects reserved floci:* tag keys. |
| UntagResource | Removes tags from a user pool's persisted tag map. |
| ListTagsForResource | Returns the persisted user-pool tags. |
User Pool Clients
| Action | Description |
|---|---|
| CreateUserPoolClient | Creates an app client for a user pool, including optional generated secret handling. |
| DescribeUserPoolClient | Returns the stored app client configuration. |
| ListUserPoolClients | Lists app clients for a user pool. |
| DeleteUserPoolClient | Deletes an app client from a user pool. |
Resource Servers
| Action | Description |
|---|---|
| CreateResourceServer | Registers a resource server and scopes for a user pool. |
| DescribeResourceServer | Returns a registered resource server. |
| ListResourceServers | Lists resource servers for a user pool. |
| UpdateResourceServer | Updates a resource server's name and scopes. |
| DeleteResourceServer | Deletes a resource server from a user pool. |
Identity Providers
| Action | Description |
|---|---|
| CreateIdentityProvider | Registers a federated identity provider on a user pool. |
| DescribeIdentityProvider | Returns a registered identity provider. |
| ListIdentityProviders | Lists a user pool's providers as name/type/date summaries. |
| UpdateIdentityProvider | Updates a provider's details, attribute mapping or identifiers. |
| DeleteIdentityProvider | Deletes an identity provider from a user pool. |
Providers can be used for generic OIDC authorization-code sign-in. Floci routes the
AWS-shaped /oauth2/authorize and /oauth2/idpresponse endpoints to the configured local
OIDC provider, exchanges the returned code, provisions or reconciles the federated user,
and issues a Cognito authorization code for the registered callback.
Only ProviderType=OIDC is supported by this flow. Floci does not implement social providers
such as Google, Facebook, Login with Amazon or SignInWithApple. The pool's own users sign in on
the hosted login page instead (see Well-Known And OAuth Endpoints below).
Two deliberate divergences from AWS, both consequences of not calling out to a third party:
- No create-time validation of
ProviderDetails. AWS resolves an OIDC provider'soidc_issuerdiscovery document while handlingCreateIdentityProvider, and rejects the call when it is unreachable. Floci storesProviderDetailsopaquely and makes no outbound request, so it also does not enforce the per-provider-type required keys. - No injected provider defaults. AWS adds keys such as
attributes_url_add_attributesto an OIDC provider's stored details; Floci returns only what was supplied.
AttributeMapping and IdpIdentifiers follow AWS's update semantics: a member the
request omits is left unchanged, and an explicitly empty map or list is what clears it.
IdpIdentifiers is echoed by CreateIdentityProvider and UpdateIdentityProvider only
when the request supplied it, whatever the stored value, while DescribeIdentityProvider
always returns it.
User Pool Domains
| Action | Description |
|---|---|
| CreateUserPoolDomain | Creates a Cognito prefix domain, or a custom domain when CustomDomainConfig.CertificateArn is given. As on AWS, the certificate must exist in ACM in us-east-1 with status ISSUED; the domain is listed under the certificate's InUseBy until it is deleted. |
| DescribeUserPoolDomain | Returns a domain's description, including CloudFrontDistribution for custom domains. |
| UpdateUserPoolDomain | Replaces a custom domain's certificate or changes the managed login version in place. The domain keeps its CloudFrontDistribution, and its InUseBy entry moves to the new certificate. |
| DeleteUserPoolDomain | Deletes a domain from its user pool. |
Custom domains
A domain created with CustomDomainConfig answers the OAuth endpoints on that host, as on AWS:
POST https://auth.example.localhost.floci.io/oauth2/token
GET https://auth.example.localhost.floci.io/oauth2/userInfo
Requests are matched on the Host header, so the name must resolve to Floci (any
*.localhost.floci.io does) and, for https, TLS must be enabled. The domain pins its user pool and
the account that created it, since these requests carry no AWS credential: a client_id from
another pool is refused with invalid_client, and an access token issued by another pool with
invalid_token. Domain names are unique across all accounts, as on AWS. The pool's openid-configuration advertises the custom-domain
URLs when one exists. Prefix domains (<prefix>.auth.<region>.amazoncognito.com) are stored but not
routed, since that hostname never reaches Floci. /login and /logout are not served on any host.
With TLS enabled, a custom domain (CustomDomainConfig set) is added to Floci's server
certificate as soon as it is created, so https://<domain> verifies without a restart; see
TLS for the accepted suffixes. A prefix domain is served under
amazoncognito.com on AWS, not by Floci, and is left alone.
Log Delivery
| Action | Description |
|---|---|
| SetLogDeliveryConfiguration | Replaces a user pool's log delivery configuration. |
| GetLogDeliveryConfiguration | Returns a user pool's log delivery configuration. |
LogLevel accepts ERROR or INFO, and EventSource accepts userNotification or
userAuthEvents. LogConfigurations holds at most 2 entries, and an event source may
appear only once across them. Each entry must name a destination:
CloudWatchLogsConfiguration, FirehoseConfiguration or S3Configuration, and a request
that omits one is rejected the way AWS rejects it. Set replaces the whole list rather
than merging, so an empty LogConfigurations is what clears it, and Get always returns
the member, as [] when nothing is configured.
The length and enum checks run before the pool is looked up, so an oversized or malformed
request naming a pool that does not exist reports the request problem rather than
ResourceNotFoundException, and every violation of them is reported in one message.
Floci stores the configuration and never delivers anything to the destination: the log group, delivery stream or bucket is not written to, and is not required to exist. Two further divergences, both deliberate:
- No pricing-tier gate. AWS refuses
userAuthEventson a pool in theESSENTIALStier withFeatureUnavailableInTierException; Floci accepts either event source whateverUserPoolTiersays. - No destination validation. AWS checks the ARN it is handed; Floci stores it as given.
Admin User Management
| Action | Description |
|---|---|
| AdminCreateUser | Creates or resends setup for a user in a user pool. |
| AdminGetUser | Returns a user's stored attributes and status. |
| AdminDeleteUser | Deletes a user from a user pool. |
| AdminSetUserPassword | Sets a user's password and permanent-password status. |
| AdminUpdateUserAttributes | Updates attributes for a user in a user pool. |
| AdminLinkProviderForUser | Links an external IdP identity to an existing user's identities attribute. |
User Operations
| Action | Description |
|---|---|
| SignUp | Creates a self-service user for an app client. |
| ConfirmSignUp | Confirms a pending self-service signup. |
| GetUser | Returns attributes for the authenticated access-token user. |
| GetUserAttributeVerificationCode | Issues a verification code for the authenticated user's email or phone_number attribute. |
| VerifyUserAttribute | Verifies an email or phone_number attribute with its issued verification code. |
| UpdateUserAttributes | Updates attributes for the authenticated access-token user. |
| ChangePassword | Changes the authenticated user's password. |
| ForgotPassword | Starts the local forgot-password flow for a user. |
| ConfirmForgotPassword | Completes the forgot-password flow by setting a replacement password. |
Authentication
| Action | Description |
|---|---|
| InitiateAuth | Authenticates app-client users through supported user-password and SRP-style flows. |
| AdminInitiateAuth | Starts an admin authentication flow for a user pool user. |
| RespondToAuthChallenge | Responds to supported Cognito auth challenges. |
User Listing
| Action | Description |
|---|---|
| ListUsers | Lists users stored in a user pool. |
Supported AuthFlow Values
InitiateAuth accepts USER_PASSWORD_AUTH, USER_SRP_AUTH, CUSTOM_AUTH, USER_AUTH,
REFRESH_TOKEN_AUTH and REFRESH_TOKEN. AdminInitiateAuth accepts ADMIN_USER_PASSWORD_AUTH,
ADMIN_NO_SRP_AUTH, USER_SRP_AUTH, USER_PASSWORD_AUTH, CUSTOM_AUTH, USER_AUTH,
REFRESH_TOKEN_AUTH and REFRESH_TOKEN.
USER_AUTH is the choice-based flow: with no PREFERRED_CHALLENGE it returns
ChallengeName=SELECT_CHALLENGE and an AvailableChallenges list drawn from what the user has
configured (PASSWORD, PASSWORD_SRP, EMAIL_OTP, SMS_OTP); with one, it goes straight to that
challenge. It requires the user pool's tier to be Essentials or higher. WEB_AUTHN and the
ConfirmSignUp session as a first-factor shortcut are not implemented yet.
Any other AuthFlow value is rejected with InvalidParameterException and no tokens are issued.
An app client only accepts the flows in its ExplicitAuthFlows: ALLOW_USER_PASSWORD_AUTH,
ALLOW_USER_SRP_AUTH, ALLOW_CUSTOM_AUTH, ALLOW_USER_AUTH, ALLOW_ADMIN_USER_PASSWORD_AUTH and
ALLOW_REFRESH_TOKEN_AUTH, or the legacy USER_PASSWORD_AUTH, ADMIN_NO_SRP_AUTH and
CUSTOM_AUTH_FLOW_ONLY. Any other flow fails with InvalidParameterException.
A client created without ExplicitAuthFlows, or with it cleared to an empty list, stores and describes an
empty list, matching AWS. It is enforced as if it had ALLOW_REFRESH_TOKEN_AUTH, ALLOW_USER_SRP_AUTH and
ALLOW_CUSTOM_AUTH, the default AWS documents for such a client: only the enforcement uses that default, not
the stored or returned value. A client that signs in with USER_PASSWORD_AUTH, ADMIN_USER_PASSWORD_AUTH or
USER_AUTH must list the matching ALLOW_ value.
User Attribute Update Verification
CreateUserPool, UpdateUserPool, and DescribeUserPool support
UserAttributeUpdateSettings.AttributesRequireVerificationBeforeUpdate for
email and phone_number.
For attributes listed in this setting, UpdateUserAttributes keeps the existing
verified value and sign-in alias active while the new value is pending. It sends
a verification code to the pending destination and returns the corresponding
entry in CodeDeliveryDetailsList. A successful VerifyUserAttribute promotes
the pending value, switches the alias, and sets the matching *_verified
attribute to true.
Without the setting, UpdateUserAttributes replaces the value immediately and
sets the matching *_verified attribute to false until verification succeeds.
A verification code is still sent to the replacement value, but Cognito no
longer retains or exposes the old value. For alias attributes, the old alias is
removed immediately and the replacement becomes usable for sign-in only after
successful verification. Incorrect or expired codes don't promote a pending
value or change its verified state.
Groups
| Action | Description |
|---|---|
| CreateGroup | Creates a group in a user pool. |
| GetGroup | Returns a user-pool group. |
| UpdateGroup | Updates a user-pool group's stored settings. |
| ListGroups | Lists groups in a user pool. |
| ListUsersInGroup | Lists users assigned to a group. |
| DeleteGroup | Deletes a group from a user pool. |
| AdminAddUserToGroup | Adds a user to a group. |
| AdminRemoveUserFromGroup | Removes a user from a group. |
| AdminListGroupsForUser | Lists the groups assigned to a user. |
Managed Login Branding
| Action | Description |
|---|---|
| CreateManagedLoginBranding | Creates the branding for an app client. |
| DescribeManagedLoginBranding | Returns a branding by its id. |
| DescribeManagedLoginBrandingByClient | Returns the branding attached to an app client. |
| UpdateManagedLoginBranding | Updates a branding's settings, assets or provided-values flag. |
| DeleteManagedLoginBranding | Deletes a branding from its app client. |
CreateManagedLoginBranding must name either UseCognitoProvidedValues or Settings; a
request with neither is rejected. One branding per app client: a second
CreateManagedLoginBranding for the same client is rejected with
ManagedLoginBrandingExistsException. ManagedLoginBrandingId must be a
version 4 UUID, and a malformed one is rejected before the lookup, as AWS does.
Assets holds at most 40 entries on create and on update.
Settings is omitted from the response when the caller supplied none, while Assets is
always returned. Members an update omits are left unchanged.
The asset-count and branding-id checks run before the pool, client or branding is looked
up, so an oversized request naming something that does not exist reports the request
problem rather than ResourceNotFoundException, and an update violating both reports them
in one message with the asset list first.
Branding is presentation for the hosted UI. Floci's hosted login page does not apply it, so it is stored and returned rather than rendered. Two divergences follow from that:
Settingsis stored opaquely. AWS validates it against a deep schema, rejecting unknown properties withInvalid settings provided. Validation errors: [{property: $.components...., errorType: UnknownProperty}]. That schema is not published, so Floci accepts any object.- A wrongly typed
Settingsreturns a client error. AWS answers that particular input withInternalErrorExceptionand a 500; Floci returnsSerializationException: Unexpected field type, which is what AWS returns for a wrongly typedAssets. Reproducing someone else's 500 seemed worse than being consistent. ReturnMergedResourcesis not honoured. Against AWS it merges Cognito's own default settings and assets into the response: on a pool with 8 configured assets it returns 38. Reproducing that needs Cognito's default corpus, so Floci returns the stored branding either way.
Well-Known And OAuth Endpoints
| Endpoint | Description |
|---|---|
GET /{userPoolId}/.well-known/openid-configuration |
OpenID discovery document |
GET /{userPoolId}/.well-known/jwks.json |
JSON Web Key Set for JWT validation |
GET /cognito-idp/oauth2/authorize |
OIDC authorization-code start endpoint |
GET /cognito-idp/oauth2/idpresponse |
OIDC provider callback endpoint |
GET, POST /cognito-idp/login |
Hosted login page for the pool's own users |
GET /cognito-idp/logout |
Ends the hosted login session |
POST /cognito-idp/oauth2/token |
OAuth authorization-code, refresh-token and client-credentials token endpoint |
The OAuth endpoints support browser-style OIDC authorization-code sign-in as well as the emulator-friendly client-credentials flow:
GET /cognito-idp/oauth2/authorizevalidates the app client and callback. Withidentity_providernaming an OIDC provider it redirects there with an opaque state and nonce. Withoutidentity_provider, or withidentity_provider=COGNITO, the pool's own users sign in on the hosted login page; the client must listCOGNITOinSupportedIdentityProviders(the default when none are given).GET /cognito-idp/loginshows a user name (or email) and password form. A correct password redirects to the registered callback with a one-time authorization code andstate, and starts a one-hour browser session (thecognitocookie), so the next authorize request returns a code without the form unless it sendsprompt=login. Wrong passwords, unknown users and unconfirmed users get the same answer until the password is right. Users with a temporary password, MFA or a pending reset are told so; those steps are not served on the page yet.GET /cognito-idp/logout?client_id=...&logout_uri=...ends the session and redirects tologout_uri, which must be one of the client'sLogoutURLs. Withredirect_uriandresponse_type=codeinstead, it ends the session and shows the login page again.- PKCE: send
code_challengewithcode_challenge_method=S256to authorize, and the matchingcode_verifierwhen redeeming the code.nonceis copied into the ID token. - Codes from the hosted login and from federated sign-in fire the Pre Token Generation trigger
with
triggerSource=TokenGeneration_HostedAuth, and the hosted login fires Pre and Post Authentication. GET /cognito-idp/oauth2/idpresponseconsumes the provider state, exchanges the provider code and redirects to the registered callback with a one-time Cognito authorization code.POST /cognito-idp/oauth2/tokenredeems that authorization code once, exchanges arefresh_tokenfor new access and ID tokens, or issues a machine token forgrant_type=client_credentials.
POST /cognito-idp/oauth2/token is intentionally emulator-friendly rather than full Cognito parity:
- It requires an existing
client_id. - It accepts
client_idandclient_secretfrom the form body or Basic auth. - Client-credentials requires a confidential app client created with
GenerateSecret=true. - Authorization-code redemption validates the client, callback URI and one-time code binding.
- It requires
AllowedOAuthFlowsUserPoolClient=trueandAllowedOAuthFlows=["client_credentials"]. - It doesn't require a Cognito domain.
- Client-credentials returns only
access_token,token_type, andexpires_in; authorization-code redemption returns the Cognito access, ID and refresh token set. - It validates requested OAuth scopes against the app client's
AllowedOAuthScopesand the pool's registered resource-server scopes. - It advertises the prefixed token endpoint in
/{userPoolId}/.well-known/openid-configuration, orhttps://<domain>/oauth2/tokenwhen the pool has a custom domain (see Custom domains above).
Sign-in Identifiers (UsernameAttributes)
Floci follows real Cognito semantics for pools created with UsernameAttributes (e.g.
--username-attributes email):
AdminCreateUser/SignUpaccept the email (or phone number) as the sign-in value, but the canonicalUsernameis an auto-generated, immutable UUID equal tosub. The supplied email is stored as a mutable alias attribute.ListUsers,AdminGetUserand Lambda triggerevent.userNameall report the UUID, never the email; the email lives in theemailattribute and inevent.request.userAttributes.email.- Sign-in resolves by the current email alias or the UUID.
SECRET_HASHis validated against the exactUSERNAMEvalue sent:Base64(HMAC-SHA256(USERNAME + clientId, clientSecret)). - In
CUSTOM_AUTH/ SRP flows,ChallengeParameters.USERNAMEechoes the UUID (as real AWS does); theRespondToAuthChallengeSECRET_HASHis validated against theUSERNAMEsent that round. ThatUSERNAMEmust still resolve to the session's user — the UUID or any of its current aliases — and a value naming a different user is rejected withNotAuthorizedException. AdminUpdateUserAttributescan change the email; afterwards sign-in works with the new email and fails with the old one, whileUsername/substay fixed.- Duplicate email/phone on
AdminCreateUser:UsernameExistsExceptionwhen the incoming alias is unverified,AliasExistsExceptionwhen it is verified (email_verified=true); passForceAliasCreation=trueto migrate a verified alias off the previous owner. OnAdminUpdateUserAttributes, changing to an in-use alias throwsAliasExistsException.
Pools without UsernameAttributes (classic pools, and pools using AliasAttributes) keep the
literal Username you supply, unchanged.
Token claims
Floci mirrors AWS's access-token / ID-token split:
- Access token:
sub,username(the UUID),scope(aws.cognito.signin.user.adminfor API sign-in),client_id,cognito:groups,jti/origin_jti. It does not carrycognito:usernameor user attributes likeemail. - ID token:
sub,cognito:username,aud, and readable user attributes (email,email_verified,phone_number,custom:*, ...). Attribute claims are filtered by the app client'sReadAttributes(an unset/empty list means all attributes are readable).
Not-found errors (ResourceNotFoundException, UserNotFoundException) return HTTP 400, matching the
Cognito JSON protocol.
Configuration
| Variable | Default | Description |
|---|---|---|
FLOCI_SERVICES_COGNITO_ENABLED |
true |
Enable or disable the service |
Examples
export AWS_ENDPOINT_URL=http://localhost:4566
# Create a user pool
POOL_ID=$(aws cognito-idp create-user-pool \
--pool-name MyApp \
--query UserPool.Id --output text \
--endpoint-url $AWS_ENDPOINT_URL)
# Create an app client
CLIENT_ID=$(aws cognito-idp create-user-pool-client \
--user-pool-id $POOL_ID \
--client-name my-client \
--generate-secret \
--allowed-o-auth-flows-user-pool-client \
--allowed-o-auth-flows client_credentials \
--allowed-o-auth-scopes notes/read notes/write \
--query UserPoolClient.ClientId --output text \
--endpoint-url $AWS_ENDPOINT_URL)
# Retrieve the generated client secret
CLIENT_SECRET=$(aws cognito-idp describe-user-pool-client \
--user-pool-id $POOL_ID \
--client-id $CLIENT_ID \
--query UserPoolClient.ClientSecret --output text \
--endpoint-url $AWS_ENDPOINT_URL)
# Register a resource server and scopes
aws cognito-idp create-resource-server \
--user-pool-id $POOL_ID \
--identifier notes \
--name "Notes API" \
--scopes ScopeName=read,ScopeDescription="Read notes" ScopeName=write,ScopeDescription="Write notes" \
--endpoint-url $AWS_ENDPOINT_URL
# Create a user
aws cognito-idp admin-create-user \
--user-pool-id $POOL_ID \
--username alice@example.com \
--temporary-password Temp1234! \
--endpoint-url $AWS_ENDPOINT_URL
# Set a permanent password
aws cognito-idp admin-set-user-password \
--user-pool-id $POOL_ID \
--username alice@example.com \
--password Perm1234! \
--permanent \
--endpoint-url $AWS_ENDPOINT_URL
# Authenticate
aws cognito-idp initiate-auth \
--auth-flow USER_PASSWORD_AUTH \
--client-id $CLIENT_ID \
--auth-parameters USERNAME=alice@example.com,PASSWORD=Perm1234! \
--endpoint-url $AWS_ENDPOINT_URL
# Create a group
aws cognito-idp create-group \
--user-pool-id $POOL_ID \
--group-name admin \
--description "Admin group" \
--endpoint-url $AWS_ENDPOINT_URL
# Add user to group
aws cognito-idp admin-add-user-to-group \
--user-pool-id $POOL_ID \
--group-name admin \
--username alice@example.com \
--endpoint-url $AWS_ENDPOINT_URL
# List groups for user
aws cognito-idp admin-list-groups-for-user \
--user-pool-id $POOL_ID \
--username alice@example.com \
--endpoint-url $AWS_ENDPOINT_URL
# Fetch the pool discovery document
curl -s "$AWS_ENDPOINT_URL/$POOL_ID/.well-known/openid-configuration"
# Get a machine access token from the OAuth endpoint
curl -s \
-X POST "$AWS_ENDPOINT_URL/cognito-idp/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "$CLIENT_ID:$CLIENT_SECRET" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "scope=notes/read notes/write"
JWT Validation
Tokens issued by Floci can be validated using the discovery and JWKS endpoints:
http://localhost:4566/$POOL_ID/.well-known/openid-configuration
http://localhost:4566/$POOL_ID/.well-known/jwks.json
Tokens include the cognito:groups claim as a JSON array when the authenticated user belongs to one or more groups.
Tokens issued by Cognito auth flows and the OAuth token endpoint use the emulator base URL plus the pool id:
http://localhost:4566/$POOL_ID
This keeps the issuer, discovery document, JWKS URL, and token endpoint internally consistent for local JWT validation while supporting LocalStack-style confidential clients and resource-server-backed scopes.