Skip to content

Repository files navigation

Traefik Passkey Authentication Plugin

⚠️ Important Notice: Due to Traefik plugin limitations that only allow standard library dependencies, this plugin implements a simplified WebAuthn verification. While it provides passkey authentication, it's recommended for internal/development use rather than production environments requiring full WebAuthn specification compliance.

For production deployments, consider using external authentication services or a full WebAuthn server with Traefik's ForwardAuth middleware.

A Traefik middleware plugin that provides WebAuthn/FIDO2 passkey authentication with user credentials specified in the plugin configuration.

Features

  • 🔐 Passkey Authentication: Modern passwordless authentication using WebAuthn
  • 👤 Configuration-Based Users: Define authorized users directly in Traefik config
  • 🍪 Session Management: Cookie-based sessions with configurable timeout
  • 🎨 Built-in Login Page: Clean, responsive authentication UI
  • 🔒 Secure: HttpOnly, Secure cookies with SameSite protection
  • 📦 No External Dependencies: Uses only Go standard library

Limitations

  • Uses simplified ECDSA P-256 (ES256) signature verification
  • Public key extraction from COSE format is simplified
  • No support for RSA keys or other algorithms currently
  • Intended for internal/development use

Configuration

Plugin Configuration

Add the plugin to your Traefik static configuration:

# traefik.yml
experimental:
  plugins:
    passkey:
      moduleName: github.com/CangioUni/traefik-passkey-plugin
      version: v0.2.0

Middleware Configuration

Configure the middleware in your dynamic configuration:

# dynamic-config.yml
http:
  middlewares:
    passkey-auth:
      plugin:
        passkey:
          users:
            - id: "user-123"
              name: "john.doe"
              displayName: "John Doe"
              credentials:
                - id: "Y3JlZGVudGlhbC1pZA"  # Base64URL-encoded credential ID
                  publicKey: "pQECAyYgASFYIPr5..."  # Base64URL-encoded attestation object
                  signCount: 0
            - id: "user-456"
              name: "jane.smith"
              displayName: "Jane Smith"
              credentials:
                - id: "YW5vdGhlci1jcmVkZW50aWFs"
                  publicKey: "pQECAyYgASFYIC3m..."
                  signCount: 0
          rpDisplayName: "My Application"
          rpId: "example.com"
          rpOrigin: "https://example.com"
          cookieName: "traefik_passkey_session"
          cookieDomain: "example.com"
          sessionTimeout: 60  # minutes

  routers:
    my-router:
      rule: "Host(`example.com`)"
      service: my-service
      middlewares:
        - passkey-auth
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

  services:
    my-service:
      loadBalancer:
        servers:
          - url: "http://backend:8080"

Configuration Parameters

Parameter Type Required Default Description
users array - List of authorized users with their credentials
users[].id string - Unique user identifier
users[].name string - Username for login
users[].displayName string - User's display name
users[].credentials array - User's passkey credentials
users[].credentials[].id string - Base64URL credential ID
users[].credentials[].publicKey string - Base64URL attestation object
users[].credentials[].signCount int - Signature counter (start with 0)
rpDisplayName string "Traefik Passkey Auth" Relying party display name
rpId string - Relying party ID (domain)
rpOrigin string - Relying party origin (full URL)
cookieName string "traefik_passkey_session" Session cookie name
cookieDomain string "" Cookie domain
sessionTimeout int 60 Session timeout in minutes

Obtaining User Credentials

To add users to your configuration, you need to register their passkeys. Use the provided register.html file:

Registration Steps

  1. Serve the registration page:

    # For local testing
    python3 -m http.server 8080
    # Then open http://localhost:8080/register.html
    
    # For production, serve from your actual domain over HTTPS
  2. Register a passkey:

    • Open the registration page in your browser
    • Enter username, display name, and RP ID (must match your domain)
    • Click "Register Passkey"
    • Follow your device's authentication prompts
    • Copy the generated JSON configuration
  3. Add to Traefik configuration:

    • Paste the user object into your users array
    • Restart Traefik or reload the configuration

Example Output

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "john.doe",
  "displayName": "John Doe",
  "credentials": [{
    "id": "dGVzdC1jcmVkZW50aWFsLWlk",
    "publicKey": "pQECAyYgASFYIFN...",
    "signCount": 0
  }]
}

Important Notes

  • HTTPS Required: Passkey registration requires HTTPS in production (localhost works for testing)
  • Domain Matching: The RP ID used during registration must match the domain where you'll use the passkey
  • Multiple Devices: Register each device separately and add all credentials to the same user

How It Works

  1. Unauthenticated Request: When a user accesses a protected route without a valid session, they're shown a login page
  2. Authentication Flow:
    • User enters their username
    • Plugin generates a WebAuthn challenge
    • User's authenticator (fingerprint, face recognition, security key) signs the challenge
    • Plugin verifies the signature against the configured public key
  3. Session Creation: On successful authentication, a secure session cookie is created
  4. Protected Access: Subsequent requests include the session cookie and are allowed through

Security Considerations

  1. HTTPS Required: Passkey authentication requires HTTPS in production
  2. Secure Cookies: Cookies are marked as HttpOnly, Secure, and use SameSite=Lax
  3. Challenge Expiry: Authentication challenges expire after 5 minutes
  4. Session Timeout: Sessions expire based on the configured timeout
  5. RP ID/Origin: Must match your domain exactly

Endpoints

The plugin creates special authentication endpoints:

  • /.auth/passkey/begin - Start authentication ceremony (POST)
  • /.auth/passkey/finish - Complete authentication (POST)
  • /.auth/passkey/logout - End session (GET/POST)

Docker Compose Example

version: '3.8'

services:
  traefik:
    image: traefik:v3.0
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.file.directory=/config"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--experimental.plugins.passkey.modulename=github.com/yourusername/traefik-passkey-plugin"
      - "--experimental.plugins.passkey.version=v1.0.0"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./config:/config
      - ./certs:/certs

  app:
    image: nginx:alpine
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls=true"
      - "traefik.http.routers.app.middlewares=passkey-auth"

Troubleshooting

"User not found" Error

  • Verify the username matches the name or id field in your configuration
  • Check for typos in the configuration

Authentication Fails

  • Ensure rpId matches your domain exactly (no protocol, no port)
  • Verify rpOrigin includes the full URL with protocol
  • Check that credentials were registered for the same domain
  • Confirm the public key is correctly base64-encoded

Session Not Persisting

  • Ensure cookies are enabled in the browser
  • Check cookieDomain matches your domain
  • Verify you're using HTTPS (required in production)

License

MIT License - See LICENSE file for details

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages