Created
November 18, 2025 16:15
-
-
Save stewartjarod/9d9a96b6f6f3b0b29031d312d0042313 to your computer and use it in GitHub Desktop.
Vercel + SST Integration Pattern - OIDC authentication and automatic environment sync
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Vercel + SST Integration Pattern | |
| // Zero-credential AWS access via OIDC + automatic environment variable sync | |
| // ============================================================================ | |
| // SST Configuration | |
| // sst.config.ts - Infrastructure orchestrator | |
| // ============================================================================ | |
| $config({ | |
| app: "your-app", | |
| providers: { | |
| aws: { region: "us-east-1" }, | |
| vercel: "~1.0.0", // Enables Vercel resource management from SST | |
| } | |
| }); | |
| export default $config({ | |
| async run() { | |
| await import("./infra/app"); // Load infrastructure modules | |
| } | |
| }); | |
| // ============================================================================ | |
| // Infrastructure Module | |
| // infra/app.ts - Vercel OIDC setup + resource provisioning | |
| // ============================================================================ | |
| import * as aws from "@pulumi/aws"; | |
| import * as vercel from "@pulumiverse/vercel"; | |
| // Step 1: Create OIDC Provider for Vercel | |
| // This allows Vercel to authenticate with AWS without long-lived credentials | |
| const oidcProvider = new aws.iam.OpenIdConnectProvider("VercelOIDC", { | |
| url: `https://oidc.vercel.com/${vercelTeamId}`, | |
| clientIdList: ["vercel.com"], | |
| thumbprintList: ["<vercel-cert-thumbprints>"] // Get from Vercel docs | |
| }); | |
| // Step 2: Create IAM Role that Vercel can assume | |
| const vercelRole = new aws.iam.Role("VercelAppRole", { | |
| assumeRolePolicy: { | |
| Version: "2012-10-17", | |
| Statement: [{ | |
| Effect: "Allow", | |
| Principal: { Federated: oidcProvider.arn }, | |
| Action: "sts:AssumeRoleWithWebIdentity", | |
| Condition: { | |
| StringEquals: { | |
| "oidc.vercel.com/<team-id>:aud": "vercel.com" | |
| }, | |
| StringLike: { | |
| "oidc.vercel.com/<team-id>:sub": "project:<project-id>:*" | |
| } | |
| } | |
| }] | |
| } | |
| }); | |
| // Step 3: Attach policies to the role (scope permissions as needed) | |
| new aws.iam.RolePolicyAttachment("VercelS3Access", { | |
| role: vercelRole.name, | |
| policyArn: "arn:aws:iam::aws:policy/AmazonS3FullAccess" | |
| }); | |
| // Step 4: Create AWS resources with SST | |
| const bucket = new sst.aws.Bucket("MyBucket", { | |
| public: false, | |
| transform: { | |
| bucket: { | |
| versioning: { enabled: true } | |
| } | |
| } | |
| }); | |
| // Step 5: Auto-sync AWS resource outputs to Vercel environment variables | |
| // This is the key pattern - SST pushes infrastructure outputs to Vercel | |
| new vercel.ProjectEnvironmentVariable("AwsRoleArn", { | |
| projectId: vercelProjectId, | |
| target: ["production", "preview", "development"], | |
| key: "AWS_ROLE_ARN", | |
| value: vercelRole.arn // IAM role ARN for OIDC authentication | |
| }); | |
| new vercel.ProjectEnvironmentVariable("AwsRegion", { | |
| projectId: vercelProjectId, | |
| target: ["production", "preview", "development"], | |
| key: "AWS_REGION", | |
| value: "us-east-1" | |
| }); | |
| new vercel.ProjectEnvironmentVariable("BucketName", { | |
| projectId: vercelProjectId, | |
| target: ["production", "preview", "development"], | |
| key: "S3_BUCKET_NAME", | |
| value: bucket.name // SST resource output automatically synced | |
| }); | |
| new vercel.ProjectEnvironmentVariable("BucketArn", { | |
| projectId: vercelProjectId, | |
| target: ["production", "preview", "development"], | |
| key: "S3_BUCKET_ARN", | |
| value: bucket.arn // Another resource output synced | |
| }); | |
| // ============================================================================ | |
| // Application Code - AWS Client Setup | |
| // packages/core/src/aws.ts or similar | |
| // ============================================================================ | |
| import { S3Client } from "@aws-sdk/client-s3"; | |
| import { awsCredentialsProvider } from "@vercel/functions/oidc"; | |
| // Dynamic credential fetching - no static secrets needed | |
| const getAwsCredentials = () => { | |
| if (process.env.VERCEL_ENV) { | |
| // In Vercel: use OIDC to get temporary credentials | |
| return awsCredentialsProvider({ | |
| roleArn: process.env.AWS_ROLE_ARN!, | |
| }); | |
| } | |
| // Local dev: use AWS CLI profile or environment credentials | |
| return undefined; | |
| }; | |
| export const s3Client = new S3Client({ | |
| region: process.env.AWS_REGION, | |
| credentials: getAwsCredentials() | |
| }); | |
| // ============================================================================ | |
| // Usage in Application Code | |
| // ============================================================================ | |
| import { PutObjectCommand } from "@aws-sdk/client-s3"; | |
| import { s3Client } from "./aws"; | |
| // Example: Upload file to S3 bucket from Vercel serverless function | |
| export async function uploadFile(key: string, body: Buffer) { | |
| await s3Client.send(new PutObjectCommand({ | |
| Bucket: process.env.S3_BUCKET_NAME!, | |
| Key: key, | |
| Body: body, | |
| })); | |
| } | |
| /* | |
| ============================================================================ | |
| How It Works | |
| ============================================================================ | |
| 1. **SST provisions AWS resources** (S3, EventBridge, Lambda, etc.) | |
| - Uses standard AWS SDK/Pulumi constructs | |
| - Resources are defined in TypeScript, versioned in git | |
| 2. **SST automatically syncs outputs to Vercel** | |
| - No manual copying of ARNs or names to Vercel dashboard | |
| - Infrastructure changes propagate automatically on deploy | |
| 3. **Vercel runtime authenticates via OIDC** | |
| - Each request generates short-lived credentials (~1 hour) | |
| - Credentials scoped to the IAM role's permissions | |
| - Zero static secrets in Vercel environment | |
| 4. **AWS SDK uses temporary credentials** | |
| - @vercel/functions/oidc handles token exchange with AWS STS | |
| - Automatic refresh before expiration | |
| - Works seamlessly with all AWS SDKs | |
| ============================================================================ | |
| Key Benefits | |
| ============================================================================ | |
| ✓ Zero long-lived credentials stored in Vercel | |
| ✓ Infrastructure-as-code with automatic env var sync | |
| ✓ Type-safe resource references (TypeScript all the way) | |
| ✓ Multi-environment support (production/preview/dev) | |
| ✓ Audit trail via CloudTrail (every request has identity) | |
| ✓ Fine-grained IAM permissions per role | |
| ✓ Works with any AWS service (S3, DynamoDB, EventBridge, SQS, etc.) | |
| ============================================================================ | |
| Deployment Flow | |
| ============================================================================ | |
| 1. Developer commits infrastructure change (new S3 bucket, etc.) | |
| 2. CI/CD runs: `sst deploy --stage production` | |
| 3. SST provisions/updates AWS resources | |
| 4. SST pushes resource outputs to Vercel as env vars | |
| 5. Vercel automatically redeploys with new env vars | |
| 6. Application code uses new resources immediately | |
| One commit, full stack deployment. No manual steps. | |
| */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment