How to let your workforce sign in to Amazon Quick Desktop with the identities they already have — using Amazon Cognito, a 60-line CloudFront Function, and zero servers. Ready-to-deploy CloudFormation included.


Architecture: Quick Desktop -> CloudFront Function -> Cognito -> IAM Identity Center
Architecture overview

The problem

Amazon Quick Desktop supports Enterprise sign-in through any OIDC-compliant identity provider: you register an Authorization Endpoint, Token Endpoint, JWKS URI, Issuer, and Client ID under Extension access, and the desktop app runs a standard Authorization Code + PKCE flow against them.

If your organization's identities live in AWS IAM Identity Center, there is a catch: Identity Center is not an OIDC authorization server you can point Quick Desktop at directly. You need an OIDC layer in front of it. Amazon Cognito is the natural choice — it speaks OIDC on the front and federates to Identity Center over SAML on the back.

That almost works out of the box. Two details break it:

  1. offline_access — Quick Desktop includes the offline_access scope in its authorize request. A Cognito public app client (no secret, which is what a desktop app must use) does not support that scope, and Cognito rejects the request outright.
  2. The extra sign-in page — without a hint, Cognito shows its own hosted sign-in page before federating. Users expect to land directly on their Identity Center (or upstream IdP) page.

Both problems live in a single HTTP request: the browser's GET /oauth2/authorize. Which means both can be fixed by a tiny proxy in front of that one endpoint.

The solution: a CloudFront Function authorize-proxy

We put an Amazon CloudFront distribution in front of Cognito's authorize endpoint, with a CloudFront Function on the viewer-request event that:

The request never even reaches the origin — the function generates the response. Everything else in the flow (token exchange, JWKS, issuer) keeps talking to Cognito directly. No Lambda, no servers, no state.

Quick Desktop
   │ GET /oauth2/authorize?...scope=openid+email+profile+offline_access...
   ▼
CloudFront Function ── 302 ──> Cognito Hosted UI (authorize)
                                   │ SAML AuthnRequest
                                   ▼
                          IAM Identity Center ──> your users sign in
                                   │ SAML assertion
                                   ▼
                               Cognito ── code ──> Quick Desktop (localhost:18080)
                                   ▲
                                   └── POST /oauth2/token (PKCE) — direct, no proxy

And what about refresh, now that offline_access is gone? Cognito issues refresh tokens based on the app client's refresh token validity (we use 30 days), not the offline_access scope. Sessions refresh silently; users are not re-prompted daily.

A bonus of this architecture: because Identity Center is registered in Cognito as a Trusted Token Issuer, the tokens support trusted identity propagation — Quick knows who the human is, not just that a valid token arrived.

What you deploy

Everything is in the accompanying repository:

Piece Tool Where it can live
Cognito user pool, hosted UI, public PKCE client, SAML IdP templates/identity.yaml identity account
CloudFront Function + distribution (the proxy) templates/edge.yaml network account
Trusted Token Issuer scripts/create-trusted-token-issuer.sh identity account
Identity Center SAML app, Quick Extension access console (no API support) identity / workload account

The design is multi-account friendly with zero cross-account IAM: every trust hop is URL-based (SAML metadata URL, OIDC issuer URL, public HTTPS origin). The only hard constraint is organizational — the account running Amazon Quick must be in the same AWS Organization as Identity Center, and everything is homed in us-east-1.

Walkthrough

Step 1 — Cognito (phase 1)

aws cloudformation deploy \
  --region us-east-1 \
  --stack-name quick-desktop-identity \
  --template-file templates/identity.yaml \
  --parameter-overrides DomainPrefix=quick-desktop-<ACCOUNT_ID>

The stack outputs everything the later steps need: UserPoolId, ClientId, HostedUiDomain, TokenEndpoint, Issuer, JwksUri — plus the two values the SAML application will ask for: SamlAcsUrl and SamlAudience.

Why two phases? Cognito needs Identity Center's SAML metadata, and Identity Center needs Cognito's ACS URL and audience. Classic chicken-and-egg. Phase 1 creates Cognito so its identifiers exist; you then create the SAML app; phase 2 feeds the metadata URL back in.

Step 2 — Identity Center customer-managed SAML application (console)

This is one of three steps CloudFormation cannot do — the Identity Center API only supports OAuth 2.0 customer-managed applications, not SAML ones.

IAM Identity Center console → Applications → Customer managed → Add application → SAML 2.0, then:

Step 3 — Cognito (phase 2: attach the IdP)

aws cloudformation deploy \
  --region us-east-1 \
  --stack-name quick-desktop-identity \
  --template-file templates/identity.yaml \
  --parameter-overrides \
    DomainPrefix=quick-desktop-<ACCOUNT_ID> \
    IdcSamlMetadataUrl='<metadata URL from step 2>'

The same stack now adds the IdentityCenter SAML IdP and flips the app client's supported identity provider from COGNITO to IdentityCenter.

Step 4 — Trusted Token Issuer

No CloudFormation resource exists for this, so it is a 10-line script around aws sso-admin create-trusted-token-issuer (idempotent — safe to re-run):

./scripts/create-trusted-token-issuer.sh <UserPoolId> <profile> us-east-1

It registers the Cognito issuer URL (https://cognito-idp.us-east-1.amazonaws.com/<pool>) as an OIDC JWT trusted token issuer, mapping the email claim to emails.value in the identity store.

Step 5 — The authorize-proxy

aws cloudformation deploy \
  --region us-east-1 \
  --stack-name quick-desktop-authz-proxy \
  --template-file templates/edge.yaml \
  --parameter-overrides CognitoAuthDomain=<HostedUiDomain output>

The interesting part of edge.yaml is the function itself. Three details in it cost us real debugging time, so they deserve a callout:

// 1. Query string values arrive URL-ENCODED. The scope separator can be
//    "%20" or "+" depending on the client. Detect it, and preserve it —
//    never decode/re-encode (that corrupts redirect_uri).
var sep = raw.indexOf('%20') >= 0 ? '%20' : (raw.indexOf('+') >= 0 ? '+' : ' ');

// 2. Drop only offline_access; keep everything else byte-identical.
if (toks[i] && toks[i] !== 'offline_access') { kept.push(toks[i]); }

// 3. Skip Cognito's own sign-in page.
qs['identity_provider'] = { value: IDP };

Then verify:

./scripts/verify-authorize-endpoint.sh "$AUTH_ENDPOINT" "$CLIENT_ID"

The script asserts — for both scope separators — that you get an immediate HTTP/2 302 with x-cache: FunctionGeneratedResponse from cloudfront, that offline_access is gone, that identity_provider was added, and that the PKCE code_challenge survived untouched.

Step 6 — Wire up Amazon Quick (console)

Manage Quick → Extension access:

Field Value
Authorization Endpoint AuthorizeEndpoint output (CloudFront)
Token Endpoint TokenEndpoint output (Cognito, direct)
JWKS URI JwksUri output
Issuer Issuer output
Client ID ClientId output

Two warnings from experience:

Step 7 — Sign in

Open Quick Desktop → Sign in with SSO. You should be bounced straight to your Identity Center sign-in (no Cognito page in between) and land back in the app. If a stale local profile interferes: rm ~/.quickwork/profiles.json.

Production hardening

edge.yaml takes three optional parameters: CustomDomain, AcmCertificateArn (us-east-1), and WebAclArn (CLOUDFRONT scope).

One WAF gotcha worth the price of this article: if you attach AWSManagedRulesCommonRuleSet, the rules EC2MetaDataSSRF_QUERYARGUMENTS and EC2MetaDataSSRF_BODY false-positive on Quick Desktop's redirect URI (redirect_uri=http://localhost:18080 pattern-matches an SSRF-to-instance- metadata attempt) and block every single authorize request with a 403. Override those two rules to Count and keep the rest in Block:

aws wafv2 update-web-acl ... \
  # RuleActionOverrides:
  #   EC2MetaDataSSRF_QUERYARGUMENTS -> Count
  #   EC2MetaDataSSRF_BODY           -> Count

Diagnosis tip: aws wafv2 get-sampled-requests --rule-metric-name AWSCommon shows exactly which managed rule fired.

Cost

No Lambda, no servers, nothing to patch.

Summary

Requirement How it is met
Quick Desktop needs OIDC Cognito user pool + public PKCE client
Users live in Identity Center Cognito SAML federation + customer-managed app
offline_access unsupported on public clients CloudFront Function strips it at the edge
Skip the Cognito sign-in page Function forces identity_provider=IdentityCenter
Silent refresh without offline_access App client refresh-token validity (30 days)
Identity-aware tokens Trusted Token Issuer (trusted identity propagation)
Multi-account URL-based trust everywhere; same-Org only for Quick↔︎Identity Center

Grab the templates, deploy the two stacks, click through the three console steps, and your Quick Desktop fleet signs in with the identities you already manage.


All templates in this post are validated with cfn-lint, and the verification script's assertions were confirmed against a production deployment of this exact architecture.