webauthn-demo

command module
v0.0.0-...-84c2b7e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 20, 2020 License: Apache-2.0 Imports: 28 Imported by: 0

README

Build Status Go Report Card GitHub

WebAuthn Server Demo (Go/Golang)

This web app is a demo for my WebAuthn server library (fxamacker/webauthn). It supports WebAuthn registration and authentication. It implements proposed REST API for FIDO2 servers.

Picture of FIDO U2F key

What's WebAuthn?

WebAuthn (Web Authentication) is a W3C web standard for authenticating users to web-based apps and services. It's a core component of FIDO2, the successor of FIDO U2F legacy protocol.

Demo WebAuthn Server Components

  • fxamacker/webauthn to parse and validate registration and authentication requests.
  • Bootstrap and jQuery for web interface.
  • gorilla/mux for routing and gorilla/sessions for session management.
  • Redis for session storage.
  • PostgreSQL for data persistence.

Current Status

This demo is not for production use because it's designed to be a demo.

System Requirements

  • Go 1.12 (or newer)
  • Tested on x86_64 but it should work on other little-endian systems supported by Go.

Installation

go get github.com/fxamacker/webauthn-demo

Running WebAuthn Demo Using Docker

$ CERTS_DIR=[folder containing cert.pem and key.pem] docker-compose up

WebAuthn demo runs at https://localhost:8443 on your Docker host.

Customizing WebAuthn Demo Using Docker

  • Edit config.json to change WebAuthn server settings as needed.
  • Edit .env as needed:
    • CERTS_DIR: folder containing cert.pem and key.pem.
    • DB_NAME: database name (default: webauthn).
    • DB_PASSWORD: database password (default: dockerpwd).
    • DB_USER: database user (default: docker).
    • DB_DATA_DIR: database storage folder.
    • CACHE_DATA_DIR: cache storage folder.
    • SESSION_KEY: base64 encoded session encryption key.
  • Run WebAuthn demo:
$ docker-compose up

WebAuthn demo runs at https://localhost:8443 on your Docker host.

Registration

Registration process consists of two steps: create credential creation options and register credentials. See signup.html, webauthn.register.js, and registration_handlers.go.

Create credential creation options:

Server handles /attestation/options request by returning credential creation options (PublicKeyCredentialCreationOptions) to client. Client then uses those options with navigator.credentials.create() to create new credentials.

// Simplified `/attestation/options` handler from registration_handlers.go
func (s *server) handleAttestationOptions(w http.ResponseWriter, r *http.Request) {
    // Get user from datastore by username.
    u, _ := s.dataStore.getUser(r.Context(), optionsRequest.Username)
    
    // Create PublicKeyCredentialCreationOptions using webauthn library.
    creationOptions, _ := webauthn.NewAttestationOptions(s.webAuthnConfig, &webauthn.User{ID: u.UserID, Name: u.UserName, DisplayName: u.DisplayName, CredentialIDs: u.CredentialIDs})

    // Save creationOptions and user info in session to verify new credential later.
    session.Values[WebAuthnCreationOptions] = creationOptions
    session.Values[UserSession] = &userSession{User: u}

    // Write creationOptions to response.
}

Register credentials:

Server verifies and registers new credentials received via /attestation/result.

// Simplified `/attestation/result` handler from registration_handlers.go
func (s *server) handleAttestationResult(w http.ResponseWriter, r *http.Request) {
    // Get saved creationOptions and user info from session.

    // Parse and verify credential in request body.
    credentialAttestation, _ := webauthn.ParseAttestation(r.Body)
    expected := &webauthn.AttestationExpectedData{
	Origin:           s.rpOrigin,
	RPID:             savedCreationOptions.RP.ID,
	CredentialAlgs:   credentialAlgs,
	Challenge:        base64.RawURLEncoding.EncodeToString(savedCreationOptions.Challenge),
	UserVerification: savedCreationOptions.AuthenticatorSelection.UserVerification,
    }    
    _, _, err = webauthn.VerifyAttestation(credentialAttestation, expected)

   // Save user credential in datastore.
   c := &credential{
	CredentialID: credentialAttestation.RawID,
	UserID:       uSession.User.UserID,
	Counter:      credentialAttestation.AuthnData.Counter,
	CoseKey:      credentialAttestation.AuthnData.Credential.Raw,
   }    
   err = s.dataStore.addUserCredential(r.Context(), uSession.User, c)

   // Write "ok" response. 
}

Authentication

Authentication process requires two steps: create credential request options and verify credentials. See signin.html, webauthn.authn.js, and authentication_handlers.go.

Create credential request options:

Server handles /assertion/options request by returning credential request options (PublicKeyCredentialRequestOptions) to client. Client then uses those options with navigator.credentials.get() to get existing credentials.

// Simplified `/assertion/options` handler from authentication_handlers.go
func (s *server) handleAssertionOptions(w http.ResponseWriter, r *http.Request) {
    // Get user from datastore by username.
    u, _ := s.dataStore.getUser(r.Context(), optionsRequest.Username)
    
    // Create PublicKeyCredentialRequestOptions using webauthn library.
    requestOptions, _ := webauthn.NewAssertionOptions(s.webAuthnConfig, &webauthn.User{ID: u.UserID, Name: u.UserName, DisplayName: u.DisplayName, CredentialIDs: u.CredentialIDs})

    // Save requestOptions and user info in session to verify credential later.
    session.Values[WebAuthnRequestOptions] = requestOptions
    session.Values[UserSession] = &userSession{User: u}

    // Write requestOptions to response.
}

Verify credentials:

Server verifies credentials received via /asssertion/result.

// Simplified `/assertion/result` handler from authentication_handlers.go
func (s *server) handleAssertionResult(w http.ResponseWriter, r *http.Request) {
    // Get saved requestOptions and user info.

    // Parse credential in request body.
    credentialAssertion, _ := webauthn.ParseAssertion(r.Body)

    // Get credential from datastore by received credential ID.
    c, _ := s.dataStore.getCredential(r.Context(), uSession.User.UserID, credentialAssertion.RawID)

    // Verify credential.
    expected := &webauthn.AssertionExpectedData{
	Origin:            s.rpOrigin,
	RPID:              savedRequestOptions.RPID,
	Challenge:         base64.RawURLEncoding.EncodeToString(savedRequestOptions.Challenge),
	UserVerification:  savedRequestOptions.UserVerification,
	UserID:            uSession.User.UserID,
	UserCredentialIDs: userCredentialIDs,
	PrevCounter:       c.Counter,
	Credential:        credKey,
    }
    err = webauthn.VerifyAssertion(credentialAssertion, expected)

    // Update authenticator counter in datastore.
    c.Counter = credentialAssertion.AuthnData.Counter
    err = s.dataStore.updateCredential(r.Context(), c)

    // Write "ok" response. 
}

Security Policy

Security fixes are provided for the latest released version.

To report security vulnerabilities, please email faye.github@gmail.com and allow time for the problem to be resolved before reporting it to the public.

License

Copyright (c) 2019-present Faye Amacker

fxamacker/webauthn-demo is licensed under the Apache License, Version 2.0. See LICENSE for the full license text.

Documentation

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL