Skip to content

feat: Add anonymous user authentication with WebAuthn passkey support#16

Merged
carnach merged 12 commits into
releasefrom
user-authentication
Feb 1, 2026
Merged

feat: Add anonymous user authentication with WebAuthn passkey support#16
carnach merged 12 commits into
releasefrom
user-authentication

Conversation

@carnach
Copy link
Copy Markdown
Owner

@carnach carnach commented Feb 1, 2026

Anonymous User Authentication with WebAuthn Passkey Support

🎯 Overview

This PR introduces a comprehensive anonymous user authentication system that allows users to:

  • Create and manage anonymous accounts without email/signup
  • Recover accounts across devices using username + passphrase
  • Use WebAuthn passkeys for passwordless authentication
  • Associate groups with their anonymous identity
  • Manage their account security and privacy

✨ Features

1. Anonymous User System

  • UUID-based accounts - Automatically generated on first visit
  • Username generation - Auto-generated usernames with manual override option
  • localStorage persistence - Client-side state management for seamless UX
  • Smart persistence - Only saves accounts that have groups or authentication configured

2. Authentication Methods

Username + Passphrase

  • SHA-256 passphrase hashing for security
  • Password manager compatibility (1Password, Bitwarden, etc.)
  • Account recovery via username/passphrase combination
  • Change passphrase functionality for existing users
  • Passphrase complexity validation with real-time visual indicators:
    • Minimum 8 characters
    • At least one uppercase letter
    • At least one lowercase letter
    • At least one number
    • At least one special character

WebAuthn Passkeys

  • Cross-platform authenticator support
  • Discoverable credentials for device-free login
  • Works with 1Password, TouchID, FaceID, Windows Hello, etc.
  • Generate and manage multiple passkeys
  • Remove passkeys when needed

3. Group Management

  • Associate visited groups with anonymous account
  • Sync group associations across devices
  • Choose to keep or remove groups when signing out
  • Automatic group list merging on account recovery

4. Account Management

  • Sign out from device - Remove account from current browser while keeping server data
  • Delete account - Permanently delete account from database
  • Group cleanup options - Choose whether to keep or remove groups locally
  • Linked/Unlinked states - Different UI based on authentication status

5. Security & Abuse Prevention

  • IP-based rate limiting - 10 requests per minute per IP
  • Applied to all 11 anonymous user endpoints
  • In-memory rate limiter with automatic cleanup
  • Smart persistence prevents database bloat from unused accounts

🏗️ Technical Implementation

Frontend Components

  • AnonymousAuthMenu (src/components/anonymous-auth-menu.tsx) - Main UI component (1028 lines)
    • Dropdown menu integration in header
    • Dialog-based account management interface
    • Real-time passphrase complexity indicators
    • WebAuthn ceremony handling
    • State management with React hooks

Backend API Routes

All routes under src/app/api/anonymous-users/:

  • POST /ensure - Create/update user record
  • GET /groups - Fetch user's associated groups
  • POST /groups - Update group associations (with auto-cleanup)
  • POST /passphrase - Create/update passphrase
  • POST /recover - Recover account by username/passphrase
  • POST /delete - Delete account permanently

Passkey endpoints under src/app/api/anonymous-users/passkey/:

  • POST /register-options - Generate WebAuthn registration challenge
  • POST /register-verify - Verify and store passkey credential
  • POST /auth-options - Generate authentication challenge
  • POST /auth-verify - Verify passkey authentication
  • POST /delete - Remove passkey from account

Database Schema

model AnonymousUser {
  id                   String   @id
  username             String?  @unique
  passphraseHash       String?  @unique
  passkeysEnabled      Boolean  @default(false)
  passkeyCredentialId  String?  @unique
  passkeyPublicKey     Bytes?
  passkeyCounter       Int?
  passkeyTransports    String?
  groups               AnonymousUserGroup[]
  createdAt            DateTime @default(now())
  updatedAt            DateTime @updatedAt
}

model AnonymousUserGroup {
  anonymousUser   AnonymousUser @relation(fields: [anonymousUserId], references: [id], onDelete: Cascade)
  anonymousUserId String
  group           Group         @relation(fields: [groupId], references: [id], onDelete: Cascade)
  groupId         String
  groupName       String
  createdAt       DateTime      @default(now())

  @@id([anonymousUserId, groupId])
  @@index([groupId])
}

Migrations

Three database migrations applied:

  1. 20260201111211_add_anonymous_users - Initial user and group tables
  2. 20260201112137_add_anonymous_usernames - Add username field with unique constraint
  3. 20260201114517_add_passkey_support - Add WebAuthn passkey fields

Dependencies Added

{
  "@simplewebauthn/browser": "^10.0.0",
  "@simplewebauthn/server": "^10.0.1"
}

Configuration Updates

  • next.config.mjs - Added Turbopack root configuration for workspace detection
  • package.json - Added dev:vercel script for Vercel CLI development

🔒 Security Considerations

Implemented

✅ SHA-256 passphrase hashing (client-side before transmission)
✅ IP-based rate limiting on all endpoints
✅ Unique constraints on username and passphraseHash
✅ WebAuthn challenge validation
✅ Dynamic rpID/origin derivation for all environments
✅ No sensitive data in localStorage (only UUIDs)
✅ Cascade deletion for user groups
✅ Password manager compatibility with proper autocomplete attributes

Considerations

⚠️ In-memory rate limiting (consider Redis for multi-instance deployments)
⚠️ Client-side hashing (consider additional server-side hashing)
⚠️ No email verification (by design for anonymous accounts)

📊 Data Hygiene

The system implements smart persistence to prevent database bloat:

  1. New users are NOT saved to database until they:

    • Associate with a group, OR
    • Set up a passphrase, OR
    • Register a passkey
  2. Existing users are automatically deleted when:

    • All groups are removed, AND
    • No passphrase is configured, AND
    • No passkey is registered
  3. Rate limiting prevents abuse and spam account creation

🧪 Testing Recommendations

Manual Testing Checklist

  • Create anonymous account with passphrase
  • Recover account on different browser/device
  • Register passkey and authenticate
  • Change passphrase while authenticated
  • Associate/disassociate groups
  • Sign out with group cleanup options
  • Delete account permanently
  • Verify rate limiting (11th request returns 429)
  • Test password manager integration
  • Test passkey with 1Password/TouchID/Windows Hello

Database Testing

  • Verify new users without groups/auth are not persisted
  • Verify users are deleted when groups are removed and no auth exists
  • Verify cascade deletion of groups when user is deleted
  • Check for orphaned records

Cross-Device Testing

  • Account recovery via passphrase works
  • Discoverable credentials allow login without username
  • Group associations sync correctly

📸 Screenshots

Anonymous Auth Menu (Dropdown)

![Menu Dropdown](placeholder - add screenshot)

Account Creation/Recovery

![Account Form](placeholder - add screenshot)

Passphrase Complexity Indicators

![Complexity Validation](placeholder - add screenshot)

Passkey Management

![Passkey Options](placeholder - add screenshot)

Sign Out Options

![Sign Out Dialog](placeholder - add screenshot)

📝 Documentation

  • ANONYMOUS_USER_CHANGES.md - Comprehensive implementation documentation
    • Rate limiting configuration
    • Account persistence logic
    • User flow diagrams
    • Testing recommendations
    • Future improvement suggestions

🔄 Breaking Changes

None. This is a new feature that doesn't affect existing functionality.

🚀 Deployment Considerations

  1. Environment Variables - No new environment variables required
  2. Database Migrations - Three migrations need to be applied
  3. Dependencies - npm install to add WebAuthn libraries
  4. Rate Limiting - Consider Redis for production multi-instance setups

📋 Checklist

  • Code follows project style guidelines
  • No TypeScript compilation errors
  • Database migrations created and tested
  • Rate limiting implemented on all endpoints
  • Password manager compatibility verified
  • WebAuthn works with multiple authenticators
  • Documentation added (ANONYMOUS_USER_CHANGES.md)
  • Commit message follows conventional commits format
  • Manual testing completed
  • Cross-browser testing completed
  • Performance testing completed

🔗 Related Issues

Closes #[issue-number] (if applicable)

👥 Reviewers

Please pay special attention to:

  • WebAuthn implementation and security
  • Rate limiting effectiveness
  • Database persistence logic
  • User experience flow

Ready for review! 🎉

- Implement anonymous user system with UUID-based accounts
- Add username/passphrase authentication with SHA-256 hashing
- Add WebAuthn/passkey support for passwordless login
- Implement passphrase complexity validation with visual indicators
- Add group association management for anonymous users
- Add account recovery via username + passphrase
- Add change passphrase functionality for authenticated users
- Add passkey management (generate/remove) for linked accounts
- Add sign out vs delete account options with group cleanup choice
- Implement IP-based rate limiting (10 req/min) on all endpoints
- Add smart persistence (only save accounts with groups or auth)
- Add password manager compatibility (1Password, etc.)
- Create Prisma schema with AnonymousUser and AnonymousUserGroup models
- Add three database migrations for user tables and passkey fields
- Position anonymous auth menu in header navigation
- Add ANONYMOUS_USER_CHANGES.md documentation

Technical details:
- Next.js 16 with Turbopack configuration
- Prisma 6.18 with PostgreSQL (Neon)
- @simplewebauthn/browser and @simplewebauthn/server v10.0.0
- localStorage for client-side auth state
- Discoverable credentials for cross-device passkey login
- Dynamic WebAuthn rpID/origin derivation for all environments
- In-memory rate limiting with automatic cleanup
Copilot AI review requested due to automatic review settings February 1, 2026 13:37
@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
my-spliit-instance Ready Ready Preview, Comment Feb 1, 2026 2:22pm

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a comprehensive anonymous user authentication system with WebAuthn passkey support, allowing users to create anonymous accounts, recover them across devices using username/passphrase, and use passkeys for passwordless authentication. The implementation includes client-side state management, 11 new API endpoints with rate limiting, database schema changes, and a feature-rich UI component.

Changes:

  • Added anonymous user authentication system with UUID-based accounts, passphrase recovery, and WebAuthn passkey support
  • Implemented IP-based rate limiting (10 req/min) across all 11 anonymous user API endpoints using in-memory storage
  • Created database schema for AnonymousUser and AnonymousUserGroup tables with three migrations

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 28 comments.

Show a summary per file
File Description
src/lib/rate-limit.ts In-memory rate limiter with IP-based tracking and probabilistic cleanup
src/components/anonymous-auth-menu.tsx 1028-line React component providing UI for account management, authentication, and group associations
src/app/layout.tsx Integration of AnonymousAuthMenu into application header
src/app/groups/recent-groups-helpers.ts Added setRecentGroups helper for bulk group list updates
src/app/api/anonymous-users/ensure/route.ts Endpoint for creating/updating user records with smart persistence
src/app/api/anonymous-users/groups/route.ts GET/POST endpoints for managing group associations with auto-cleanup
src/app/api/anonymous-users/recover/route.ts Account recovery via username/passphrase authentication
src/app/api/anonymous-users/passphrase/route.ts Passphrase creation and update endpoint
src/app/api/anonymous-users/delete/route.ts Account deletion endpoint
src/app/api/anonymous-users/passkey/register-options/route.ts WebAuthn registration challenge generation
src/app/api/anonymous-users/passkey/register-verify/route.ts WebAuthn registration verification and credential storage
src/app/api/anonymous-users/passkey/auth-options/route.ts WebAuthn authentication challenge generation
src/app/api/anonymous-users/passkey/auth-verify/route.ts WebAuthn authentication verification
src/app/api/anonymous-users/passkey/delete/route.ts Passkey removal endpoint
prisma/schema.prisma Added AnonymousUser and AnonymousUserGroup models with relations to Group
prisma/migrations/* Three migrations adding tables and fields for anonymous users and passkeys
package.json Added @simplewebauthn/browser and @simplewebauthn/server dependencies
next.config.mjs Added Turbopack root configuration
ANONYMOUS_USER_CHANGES.md Comprehensive documentation of implementation and data hygiene approach

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/app/api/anonymous-users/passkey/auth-verify/route.ts
Comment thread src/lib/rate-limit.ts
Comment on lines +26 to +32
const user = await prisma.anonymousUser.findUnique({
where: {
username: body.username,
passphraseHash: body.passphraseHash,
},
include: { groups: true },
})
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The findUnique query uses composite fields (username and passphraseHash) but Prisma's findUnique only works with fields that have a unique constraint. In the schema, username and passphraseHash have separate unique constraints, but there's no composite unique constraint on both fields together. This query should use findFirst instead to avoid runtime errors.

Copilot uses AI. Check for mistakes.
Comment thread src/app/api/anonymous-users/recover/route.ts Outdated
Comment thread src/components/anonymous-auth-menu.tsx
Comment on lines +24 to +27
try {
await prisma.anonymousUser.delete({
where: { id: body.id },
})
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The account deletion endpoint allows any user to delete any account by simply knowing the userId. There's no authentication check to verify that the requester owns the account they're trying to delete. An attacker who discovers or guesses a user's UUID could permanently delete their account and all associated data. Add proper authorization checks to ensure only the account owner can delete their account.

Copilot uses AI. Check for mistakes.
Comment thread src/app/api/anonymous-users/passphrase/route.ts
Comment thread src/components/anonymous-auth-menu.tsx Outdated
Comment thread src/app/groups/recent-groups-helpers.ts Outdated
Comment thread src/app/api/anonymous-users/passkey/auth-verify/route.ts Outdated
@carnach
Copy link
Copy Markdown
Owner Author

carnach commented Feb 1, 2026

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link
Copy Markdown

Copilot AI commented Feb 1, 2026

@carnach I've opened a new pull request, #17, to work on those changes. Once the pull request is ready, I'll request review from you.

…ported the type to satisfy generateAuthenticationOptions
omitted transports from allowCredentials, which resolves the compile error while keeping the flow intact
Installed latest baseline-browser-mapping (package.json and package-lock.json updated
…ions (#17)

* Initial plan

* Address code review comments - error handling, validation, and type fixes

Co-authored-by: carnach <26198260+carnach@users.noreply.github.com>

* Add documentation, improve error messages, and enforce passphrase complexity

Co-authored-by: carnach <26198260+carnach@users.noreply.github.com>

* Add security review notes documenting remaining architectural concerns

Co-authored-by: carnach <26198260+carnach@users.noreply.github.com>

* Add environment variable validation in security documentation

Co-authored-by: carnach <26198260+carnach@users.noreply.github.com>

* Fixed the error by selecting passkeyTransports in the query so it can be referenced.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: carnach <26198260+carnach@users.noreply.github.com>
Co-authored-by: carnach <carnach@users.noreply.github.com>
Co-authored-by: carnach <gevert@gmail.com>
@carnach carnach merged commit ec69ba6 into release Feb 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants