WebRTC Viewer Temporary Credentials PoC (P6)
Status
- P6-0: Feasibility report — PROCEED verdict
- P6-1: AWS IAM test resources created, STS AssumeRole + inline session policy verified
- P6-2: Local code PoC — this document
Architecture Decision
Existing Auth.js + Postgres permission architecture remains unchanged
The permission chain is:
- Auth.js (Google / Apple / Email Magic Link) or App Bearer Token → identity
- DeviceOwnership +
roleAllowsLegacyCapability→live_viewcapability gate assertDeviceServiceAccess→ device existence, ownership, role, provisioning check- STS AssumeRole + inline session policy → temporary, single-channel-scoped AWS credentials
Why not Cognito User Pool?
- Auth.js already handles web authentication (Google / Apple / Magic Link)
- App Bearer tokens handle mobile API auth
- Introducing Cognito User Pool would:
- Duplicate the identity layer
- Require migrating existing users
- Add complexity for no benefit (the backend proxies STS, not the browser)
Why STS AssumeRole + inline session policy?
- No new identity system required — works with existing IAM
- Single-channel scoping via inline session policy (verified in P6-1)
- Short TTL (15 minutes default) limits exposure
- Server-side assume — credentials never touch long-term AWS keys
- No Cognito Identity Pool needed — the browser calls the Next.js API, which proxies STS
AWS Resources Created (P6-1)
See P6-1 Report for full details. Summary:
| Resource | ARN | Purpose |
|---|---|---|
| IAM Role | arn:aws:iam::288669178338:role/aovis-webrtc-viewer-dev-role | Trusts aovis-backend-service user; base policy allows KVS viewer actions on test channel |
| Managed Policy | arn:aws:iam::288669178338:policy/aovis-webrtc-viewer-dev-base-policy | kinesisvideo:GetSignalingChannelEndpoint/IceServerConfig/ConnectAsViewer on test channel |
| Backend policy v6 | aovis-backend-service-policy + StsAssumeWebrtcViewerRole | sts:AssumeRole on the viewer role only |
IAM Permissions Boundary
The inline session policy used on every AssumeRole call restricts to exactly:
{
"Effect": "Allow",
"Action": [
"kinesisvideo:GetSignalingChannelEndpoint",
"kinesisvideo:GetIceServerConfig",
"kinesisvideo:ConnectAsViewer"
],
"Resource": "${channelArn}"
}
- No
kinesisvideo:* - No
s3:*,bedrock:*,iam:*,ec2:* - No
kinesisvideo:ConnectAsMaster - No
kinesisvideo:PutMedia - No wildcard
*in Resource
Environment Variables
| Variable | Default | Max | Purpose |
|---|---|---|---|
WEBRTC_VIEWER_ROLE_ARN | (empty) | — | STS role ARN to assume. When empty, API returns credential_not_configured (503) |
WEBRTC_VIEWER_SESSION_SECONDS | 900 | 3600 | STS session duration. Clamped at 3600 |
Region is reused from AWS_REGION / awsConfig.region.
API Response Schema
GET /api/devices/[id]/webrtc
200 OK (Cache-Control: no-store, Cross-Origin-Resource-Policy: same-origin)
{
"channel_arn": "arn:aws:kinesisvideo:us-east-1:...",
"region": "us-east-1",
"endpoints": {
"wss": "wss://...kinesisvideo.us-east-1.amazonaws.com",
"https": "https://...kinesisvideo.us-east-1.amazonaws.com"
},
"ice_servers": [
{
"uris": ["turn:...:443?transport=udp"],
"username": "...",
"credential": "..."
}
],
"credentials": {
"accessKeyId": "ASIA...",
"secretAccessKey": "...",
"sessionToken": "...",
"expiration": "2026-05-18T13:53:04.000Z"
}
}
Error responses
| Status | error | When |
|---|---|---|
| 401 | unauthorized | No valid session/token |
| 403 | forbidden_device_role | Role lacks live_view |
| 404 | device_not_found | No device or no active ownership |
| 409 | device_not_provisioned | kvsChannelArn is null |
| 503 | credential_not_configured | WEBRTC_VIEWER_ROLE_ARN not set |
| 502 | signaling_error | STS / KVS API failure |
Browser Credential Security Rules
- Credentials are never stored in
localStorage,sessionStorage, orwindowglobal - Credentials exist only in React component state / refs
console.logof credentials is prohibited (lint enforcement recommended)- CSP headers restrict what scripts can execute
- STS credentials expire in 15 minutes (configurable)
- Each session is scoped to exactly one signaling channel ARN
- On component unmount, all references are cleared
What This PoC Can Verify
- ✅ Backend auth chain (Auth.js + App token + DeviceOwnership + role check)
- ✅ STS AssumeRole with inline single-channel policy works
- ✅ Temporary credentials have
ASIAprefix (not long-termAKIA) - ✅ Cross-channel access is denied
- ✅ S3 / Bedrock / KVS media / list operations are denied
- ✅ API returns
Cache-Control: no-storeandCross-Origin-Resource-Policy: same-origin - ✅ Frontend initializes
SignalingClientfromamazon-kinesis-video-streams-webrtc - ✅ Frontend handles
credential_not_configured,no_master,signaling_errorstates - ✅ Frontend does not leak credentials to persistent storage
What Cannot Be Verified Yet
- ❌ Live video playback — requires a hardware IPC master connected to the signaling channel (no NEXA Prime 4K unit available yet)
- ❌ Mobile app viewer — this PoC only covers browser-based viewer via Next.js
- ❌ End-to-end latency — no master stream to measure
- ❌ Multi-device production role design — base policy currently scoped to one test channel
Files Changed
| File | Change |
|---|---|
lib/aws/sts-webrtc.ts | New — STS helper: buildWebrtcViewerSessionPolicy, assumeWebrtcViewerRole, WebRtcRoleNotConfiguredError |
lib/env.ts | Added webrtcViewerRoleArn, webrtcViewerSessionSeconds (clamped to 3600) |
app/api/devices/[id]/webrtc/route.ts | Integrated assumeWebrtcViewerRole; added region, credentials to response; added Cache-Control: no-store and Cross-Origin-Resource-Policy: same-origin headers; returns credential_not_configured (503) when role unconfigured |
components/devices/webrtc-live-viewer.tsx | Added region, credentials to config type; replaced debug-only view with SignalingClient integration; added connecting, no_master, credential_not_configured states; removed debug cards |
lib/__tests__/sts-webrtc.test.ts | New — 25 tests covering policy structure, action constraints, env defaults, route source analysis, component behavior |
package.json | Added amazon-kinesis-video-streams-webrtc dependency |
package-lock.json | Updated |
docs/aws-webrtc-viewer-temporary-credentials-poc.md | New — this document |
Dependencies Added
[email protected]— AWS KVS WebRTC SDK for browser signaling
Cleanup Plan
After production rollout:
- Create production IAM role
aovis-webrtc-viewer-role(naming without-dev-suffix) - Update
aovis-backend-service-policy— removeaovis-webrtc-viewer-dev-rolereference, add production role - Delete
aovis-webrtc-viewer-dev-roleandaovis-webrtc-viewer-dev-base-policy - Clean up
aovis-backend-service-policystale versions
Production TODO
- Production role name: decide on
aovis-webrtc-viewer-rolevs keep dev naming - Multi-device base policy: role base policy should cover
aovis-webrtc-*channels; inline session policy narrows to single device - EC2 instance profile: migrate from IAM user key to instance profile for production backend
- CSP hardening: add
script-srcandconnect-srcrestrictions for WebRTC endpoints - App bearer token: already supported by
assertDeviceServiceAccess— mobile app can reuse the same API - Monitoring: STS throttling (
AssumeRolehas 60 req/s default limit per account)
Document version: 1.0 (2026-05-18) Related: P6-0 Feasibility Report | P6-1 Verification Report