@bandf/framework-identity
v1.7.0
Published
Identity & authentication (MetaMask/SIWE, email) for the bandf framework.
Readme
@bandf/framework-identity
@bandf/framework-identity gives BandF apps one session model with two ways to prove identity: MetaMask Sign-In with Ethereum and email magic links. It provides React controls, vanilla custom elements, and imperative adapters over the same browser session.
Contents
- Choose an interface
- React controls
- HTML custom elements
- Imperative adapters
- Session contract
- Server requirements and security
- Related links
Choose an interface
| App code | Interface |
| ---------------------- | -------------------------------------------------------------------------- |
| React | MetamaskLogin and EmailLogin from @bandf/framework-identity |
| Compiled HTML/Markdown | <metamask-login> and <email-login> injected by the framework when used |
| Custom UI or workflow | createMetamaskAuth from /core or createEmailAuth from /email |
All interfaces use the framework's /bandf/token/* routes and persist the same bearer session.
React controls
import { EmailLogin, MetamaskLogin } from "@bandf/framework-identity";
export function LoginControls() {
return (
<nav>
<MetamaskLogin onLogin={(address) => console.log(address)} />
<EmailLogin onLogin={() => console.log("signed in")} />
</nav>
);
}Both controls derive the API base from @bandf/framework-state unless apiBase is supplied. They restore a valid stored session on mount and expose onLogin, onLogout, and onError callbacks. className styles the trigger; ariaLabel overrides its accessible name. EmailLogin also accepts an input placeholder.
The trigger is a closed red lock when signed out and an open green lock when signed in. The package stylesheet is imported by the React entry point.
HTML custom elements
The HTML compiler supplies the browser entry when a view uses the identity elements:
<metamask-login button-class="login-button"></metamask-login>
<email-login
button-class="login-button"
placeholder="[email protected]"
></email-login>
<script>
document
.querySelector("metamask-login")
.addEventListener("login", (event) => {
console.log(event.detail.address);
});
</script>Both elements accept api-base and button-class. The email element also accepts placeholder. They dispatch login, logout, and autherror; the error event carries event.detail.message, and the MetaMask login event carries event.detail.address.
The browser entry also exposes:
globalThis.BandfIdentity = { createMetamaskAuth, createEmailAuth };Imperative adapters
Use the cores when the built-in controls do not fit the interface.
import { createMetamaskAuth } from "@bandf/framework-identity/core";
const auth = createMetamaskAuth({ apiBase: "http://localhost:3000/dev" });
const address = await auth.login();
await fetch("/dev/private", {
headers: auth.authorizedHeaders({ Accept: "application/json" }),
});The MetaMask handle exposes hasProvider, getAddress, login, logout, onChange, getToken, authorizedHeaders, and checkSession. onChange() returns an unsubscribe function for its EIP-1193 listeners.
import { createEmailAuth } from "@bandf/framework-identity/email";
const auth = createEmailAuth({ apiBase: "http://localhost:3000/dev" });
await auth.requestLink({ email: "[email protected]" });
// Call during page boot. It exchanges ?lt= when present and removes it from the URL.
const completed = await auth.completeFromUrl();The email handle exposes requestLink, completeFromUrl, getToken, logout, authorizedHeaders, and checkSession. Both factories accept a custom Web Storage-compatible storage; the default is localStorage.
Session contract
A successful login stores:
bandf-jwt: the bearer JWT;bandf-fetch-config: a serialized no-cache fetch configuration carrying the bearer header.
authorizedHeaders() reads the current token and merges Authorization into caller headers. checkSession() asks /bandf/token/check whether the stored token remains valid without prompting the wallet or sending email. logout() removes both entries.
MetaMask and email sessions are interchangeable at the bearer-token layer. A valid stored JWT is the browser's session authority. Switching from one non-empty MetaMask account to another does not rebind or invalidate that JWT automatically; use explicit logout when an application needs wallet-account switching to end the session.
Server requirements and security
The built-in routes implement these flows:
sequenceDiagram
participant B as Browser
participant I as BandF identity routes
participant W as Wallet or inbox
participant D as Supabase identities
B->>I: request challenge or magic link
I->>W: wallet signature request or email link
W-->>B: signed proof
B->>I: exchange proof
I->>D: resolve allowlisted principal
D-->>I: identity or deny
I-->>B: shared bearer JWT- MetaMask signs a SIWE message bound to the request origin, domain, and a five-minute signed nonce.
- Email links are same-origin and carry a fifteen-minute signed token.
- Identity JWTs use HS512, audience
bandf-identity, and a configurable lifetime that defaults to seven days. JWT_SIGNING_KEYmust be configured and at least 86 characters.- Supabase identity lookup fails closed: without the configured Service, no principal can log in.
- Email requests return the same public response for known and unknown addresses to avoid identity enumeration.
Nonce and magic-link tokens are stateless. They are not consumed in a server-side one-time store, so their short expiry bounds replay. Protected app routes still need the correct OAS bearer security declaration; rendering a login control does not secure an endpoint.
