Skip to content

e280/authlocal

Repository files navigation

🔒 Authlocal.org Developer Readme

Not a developer? See the User Guide instead

🏛️ Federated – your app gets user logins from authlocal.org popup
🔑 Elliptic – identities are @noble/ed25519 keypairs
📱 Clientside – statically deployed, no api servers
📜 Protocol – permissionless integration, do it your way
🥧 Easy as pie – setup your app with two easy snippets
💖 MIT Licensed – totally free and open source

A free login system for the world wide web

  • Try out the example integration live demo here: https://authlocal.org/app/
  • Your users click "Login", pick an identity, then boom! — they're logged into your site.
  • Next, you can use claim tokens, for your server to verify user requests.

Authlocal installation and setup

Quick start

  1. Pick one of these javascript install techniques
    1. HTML install technique
      Put this script into your html <head>:
      <script type="module">
        import {install} from "https://authlocal.org/install.bundle.min.js"
        const auth = await install()
        auth.on(login => console.log("auth", login))
      </script>
    2. Webdev install technique
      Install the package into your project:
      npm install @e280/authlocal
      Write this in your app's js entrypoint (like main.ts):
      import {install} from "@e280/authlocal"
      const auth = await install()
      auth.on(login => console.log("auth", login))
  2. Put this stylesheet into your html <head>:
    <link rel="stylesheet" href="https://authlocal.org/themes/basic.css"/>
  3. Place these new auth elements anywhere in your html <body>:
    <auth-user></auth-user>
    <auth-button></auth-button>
  4. Take it for a spin! You can now login, and logout.

Alternative setup: headless install without UI

Use this technique if you want to make your own UI, and don't want to load any of the authlocal elements.

  1. Create the Auth object manually, and trigger the initial load from storage:
    import {Auth} from "@e280/authlocal"
    
    const auth = new Auth()
    await auth.loadLogin()
    
    auth.on(login => console.log("auth", login))
  2. You can trigger the authlocal popup, prompting the user to login, like this:
    await auth.popup()
    • but remember, the call must originate from a user action like clicking a button, otherwise the browser's popup blocker will ignore it.

Understanding logins

You must never send the login data anywhere, it stays on the user's device.

The login is automatically persisted in localStorage for 7 days.

The anatomy of a login:

  • login.sessionId — id of the login session hex string
  • login.nametag.id — user identity public key hex string
  • login.nametag.label — user's chosen nickname
  • login.expiresAt — js timestamp of when this login expires
  • login.isExpired() — returns true if the login is expired now
  • login.signClaim(options) — sign a claim (more on this later)

Authlocal's custom html elements

auth-button

<auth-button></auth-button>

It's a "Login" button, that when clicked, spawns an Authlocal popup.

When the user is logged in, the button changes to a "Logout" button, which when clicked, will log the user out.

In javascript you can listen for logins/logouts via the button like this:

const authButton = document.querySelector("auth-button")

authButton.on(login => console.log("auth", login))
  //       👆
  // listen to *this* specific button

authButton.auth.on(login => console.log("auth", login))
  //        👆
  // listen to *any* button

auth-user

<auth-user></auth-user>

Displays the user's own logged-in identity. Shows nothing when logged-out.

auth-sigil

<auth-sigil hex="c9185ef07bc9f24ca856f2a178e59f85d0d53d22a14b26b434b58a22c9a872fb"></auth-sigil>

Take a hex-encoded id, and present to the user as a sigil.

If the user hovers over the sigil, the full thumbprint is shown in the tooltip.

If the user clicks the sigil, the full thumbprint is copied to the clipboard.


Logins are really about signing claims

Okay, so you have a user's login. Now what?

  • The purpose of a login is to sign claims on the user's behalf.
  • You can then send claims to your server, and verify them.

The purpose of a claim is to say "on behalf of this user, my frontend says..."

  • "...they want to post this message"
  • "...they want to change their avatar"
  • "...they want to buy this microtransaction"
  • Stuff like that.

Claims are secured cryptographically

  • Claims contain cryptographic proof that they stem from a user's login.
  • No attacker can forge a claim for somebody else's identity.

Sign a claim

import {Time} from "@e280/authlocal"

const claimToken = await login.signClaim({

  // any json-friendly data you want
  claim: {message: "i love ice cream"},

  // when should this claim expire?
  expiresAt: Time.future.hours(24),
})

Verify a claim

import {verifyClaim} from "@e280/authlocal/core"

const {claim, proof} = await verifyClaim({
  claimToken,
  appOrigins: ["https://e280.org"],
    //                   |
    //    your website origin goes here
})

proof.sessionId
  // id for this login session, looks like:
  // "ff730fe2d725aa5210152975212d1068d7fe28ae22b5e62337a4cde42215187a"

proof.nametag.id
  // user identity id, looks like:
  // "a08263e70a0a48a07e988a7c0931ada6b0a38fa84bf367087b810c614a4c2070"

proof.nametag.label
  // user identity nickname, looks like:
  // "Michael Scott"
  • You can verify claims on the clientside or serverside.
  • You must specify what appOrigins you expect to receive claims from (this prevents phishing attacks).

How are claims secure?

Authlocal gives your app a login session keypair, and a proof token signed by the user's identity keypair.

Your frontend can then create a claim — like "user orders a large pizza" — signed by the session keypair.

Your server verifies the proof, then the claim — proving the user authorized the session, and the session authorized the claim.


Authlocal glossary

  • Authority — the website that provides login sessions (authlocal.org)
    • authorityOrigin is the provider's origin, eg https://authlocal.org
  • App — the third party website receiving login sessions (your website)
    • appOrigin is your app origin, eg https://e280.org
  • Keypair — an ed25519 keypair
    • .id is the public key (64 character hex string)
    • .secret is the private key (64 character hex string)
  • Identity — a keypair with a label string
  • Seed — text snippet or .seed file that stores an identity
  • Nametag — the public data associated with a user's identity
    • .id is the public key (64 character hex string)
    • .label is a nickname (max 32 character string)
  • Thumbprint — easier-to-read version of an id
    • thumbprint => dozmut.winpex.linner.forsep.KgisJ8Pdgey1HC4o8cG59NaLYSoRTiHfA
    • sigil (first two words) => dozmut.winpex
    • preview (first four words) => dozmut.winpex.linner.forsep
    • bulk (second part) => KgisJ8Pdgey1HC4o8cG59NaLYSoRTiHfA
  • Login — a login session
    • is private, should never leave the user's device
    • .nametag contains the identity's id and label
    • .expiresAt js time of the moment this login expires
    • .isExpired() returns true if the login is now expired
    • .signClaim(options) sign a claim
  • Proof — provenance for login
    • is a token signed by an identity
    • is public, can be shared around
    • .nametag contains the identity's id and label
    • .sessionId proves a login session is blessed by an identity
  • Claim — arbitrary claim your frontend can make
    • is a token signed by the login session, verifiable on your server
    • is public, can be shared around
    • includes the proof token (thus nametag and sessionId)
    • includes any arbitrary claim data you want
    • verified by verifyClaim(options)

Common code snippets

💁 Notice:
Authlocal uses a number of utilities from our library @e280/stz,
To interact with these, you should import them from stz manually.

Thumbprint conversions

import {Thumbprint} from "@e280/stz"
  • id to thumbprint
    Thumbprint.fromHex("005636bab2c73223ccf56f8112432212f57f01ef61452762cd142acd61ed44ed")
      // "dozmut.winpex.linner.forsep.KgisJ8Pdgey1HC4o8cG59NaLYSoRTiHfA"
  • id to sigil
    Thumbprint.sigil.fromHex("005636bab2c73223ccf56f8112432212f57f01ef61452762cd142acd61ed44ed")
      // "dozmut.winpex"
  • thumbprint to id
    Thumbprint.toHex("dozmut.winpex.linner.forsep.KgisJ8Pdgey1HC4o8cG59NaLYSoRTiHfA")
      // "005636bab2c73223ccf56f8112432212f57f01ef61452762cd142acd61ed44ed"

Authlocal seed text format

  • Seeds can be copy-pasted, or live in a my-identity.seed file. Seed text looks like this:
    "mopfed.nimrut"
     linler.torhul.datmyn.binwep
     dilmyr.lagruc.nopwyl.witfur
     fasnes.sonpes.fostyr.foddur
     datter.diswyl.dotfer.wannul
     nomsum
    
  • There can be any number of seeds in the text
  • Whitespace and funky characters are ignored. this is a valid seed:
    ""linlertorhuldatmynbinwepdilmyrlagrucnopwylwitfurfasnessonpesfostyrfoddurdatterdiswyldotferwannulnomsum
    
  • Seeds have three parts:
    • the label is a handy nickname that the user assigned, expressed in json format.
      "mopfed.nimrut"
      
    • the 256-bit ed25519 private key, expressed as 16 bytename words.
      linler.torhul.datmyn.binwep
      dilmyr.lagruc.nopwyl.witfur
      fasnes.sonpes.fostyr.foddur
      datter.diswyl.dotfer.wannul
      
    • the 2-byte sha256 checksum ensures the private key wasn't mistyped.
      nomsum
      

Strategies for user-sovereign auth

Users will lose their identities, so have a recovery plan

When you sell a service or digital goods to a user's Authlocal identity, you should have a recovery mechanism, or people will get cranky.


💖 Authlocal is free and open source

  • Authlocal is an https://e280.org/ project.
  • Open github issues or discussions if you have any questions.
  • Like the project? Star it on github, it's the only way we're paid.

About

User-sovereign login system for everybody

Topics

Resources

License

Stars

Watchers

Forks