Felix

Allows user to create a new account or log in by providing a passkey. WebAuthN/Fido2 integration.

Package Version Hex Docs

JS only

This library is bindings for the SimpleWebAuthn server package. Therefore it only works on JS server environments.

I have a prototype for pure Gleam implementation but it is incomplete, get in touch if you need the use of an erlang based implementation.

Usage

npm i --save @simplewebauthn/server
gleam add felix@1

Verify registration

import felix/server

pub fn verify_registration(response: String) {
  // generated for each registration and sent to the client earlier
  let expected_challenge =
    bit_array.base64_url_encode(<<"secret challenge">>, False)

  use #(verified, info) <- promise.map_try(server.verify_registration(
    response: response,
    expected_challenge: expected_challenge,
    expected_origin: "http://localhost:8080",
    // rpid matches origin without scheme and port
    expected_rpid: "localhost",
    require_user_presence: False,
    require_user_verification: False,
  ))
  case verified {
    True ->
      // save the info.public_key in your database
      Ok(Nil)
    False -> Error("invalid")
  }
}

NOTE: client side registration can be handled using the plinth library bindings to the Web credentials API.

import plinth/browser/credentials
import plinth/browser/credentials/public_key

pub fn main(){
  let assert Ok(container) = credentials.from_navigator()
  let options =
    public_key.creation(
      <<"secret challenge">>,
      public_key.ES256,
      "New service",
      <<"my user id">>,
      "bob@example.com",
      "Bob",
    )
  use response <- promise.try_await(public_key.create(container, options))
  let response = json.to_string(public_key.to_json(response))
  // send to server to validate_registration
}

Verify authentication

import felix/server

pub fn verify_authentication_test(response) {
  // generated for each authentication
  let expected_challenge =
    bit_array.base64_url_encode(<<"another secret">>, False)
  let public_key = todo as "pull from DB"

  use #(verified, info) <- promise.map_try(server.verify_authentication(
    response: response,
    expected_challenge: expected_challenge,
    expected_origin: "http://localhost:8080",
    expected_rpid: "localhost",
    public_key: public_key,
    require_user_verification: False,
  ))
  verified
  |> should.be_true
  info.user_verified
  |> should.be_true
  info.credential_device_type
  |> should.equal(server.MultiDevice)
  info.credential_backed_up
  |> should.be_true
  info.origin
  |> should.equal("http://localhost:8080")
  info.rp_id
  |> should.equal("localhost")
  Ok(Nil)
}

NOTE: client side registration can be handled using the plinth library bindings to the Web credentials API.

import plinth/browser/credentials
import plinth/browser/credentials/public_key

pub fn main(){
  let assert Ok(container) = credentials.from_navigator()
  let options = public_key.request(<<"another secret">>)
  use response <- promise.try_await(public_key.get(container, options))
  let response = json.to_string(public_key.to_json(response))
  // send to server to validate_authentication
}

Further documentation can be found at https://hexdocs.pm/felix.

Development

gleam run   # Run the project
gleam test  # Run the tests

Credit

Created for EYG, a new integration focused programming language.

Search Document