Skip to main content

understanding nostr integration (nip-07): secure authentication for your postfun apps

the "signer" principle: your private key, always in your control

at postfun, security and user self-custody are paramount. unlike traditional web applications that rely on username/password combinations or email-based logins, postfun uses the nostr protocol for its core identity and authentication. specifically, we leverage nip-07, a standard that allows web applications to interact with a nostr key manager (like our postfun chrome extension) without ever directly accessing the user's private key (nsec).

think of it this way:

  • your postfun extension: this acts as your secure "signer." it's like a hardware wallet or a physical key that holds your nsec securely inside your browser. it has the ability to sign nostr events.
  • postfun.xyz website (or your dapp): this acts as the "requester." it needs certain nostr events to be signed (like a login event or a trade execution event).
  • the interaction: the requester asks the signer (your extension) to sign an event. the signer then prompts you for permission. if you approve, the signer uses your nsec to sign the event locally within the extension and returns the signed event to the requester. your nsec never leaves the extension.

this model is critical for ensuring that you retain full control and custody of your nostr identity and, by extension, your postfun account.

the login flow: step-by-step for frontend developers

to integrate postfun's secure authentication into your own web applications (dapps), you'll follow this standard nip-07 flow. this process typically occurs when a user clicks a "login" or "connect wallet" button on your frontend.

1. check for the window.nostr provider

the postfun chrome extension (and other nip-07 compatible extensions) injects a window.nostr object into the browser's javascript environment. your application should first check for its presence.

// check if a nip-07 compatible extension is installed and active
if (typeof window.nostr !== 'undefined') {
console.log("nostr extension detected!");
// proceed with login logic
} else {
console.error("nostr extension not found. please install postfun extension to log in.");
// prompt user to install the extension
}

2. generate a unique login challenge

to prevent replay attacks (where an attacker uses an old signed login event), your backend must issue a unique, unpredictable "challenge" string for each login attempt. the frontend will include this challenge in the nostr event it asks the user to sign.

// example: fetch a challenge from your backend
// your backend would generate this challenge and store it temporarily
const response = await fetch('https://postfun.xyz/api/auth/challenge');
const { challenge } = await response.json();

console.log("received challenge:", challenge);

3. craft the nostr event template

you'll create a standard nostr event object (but without a signature yet). for login, the kind 22242 ("ephemeral event") is commonly used as it's not meant to be stored by relays long-term. the content field will contain the unique challenge.

const eventtemplate = {
kind: 22242, // ephemeral event kind
created_at: math.floor(date.now() / 1000), // current unix timestamp
tags: [], // optional tags, can be empty for login
content: json.stringify({ challenge: challenge, purpose: "postfun login" }) // include your challenge here
};

console.log("event template to sign:", eventtemplate);

4. request signature from the extension

now, call window.nostr.signevent() on the event template. this is the crucial step that triggers the extension's user prompt.

try {
const signedevent = await window.nostr.signevent(eventtemplate);
console.log("signed event received from extension:", signedevent);
// now send this signedevent to your backend for verification
} catch (error) {
console.error("user denied signing or an error occurred:", error);
// handle user cancellation or extension error
}

5. send the signed event to your backend

your backend will verify this signedevent to authenticate the user. refer to the backend api reference for the exact endpoint.

const loginresponse = await fetch('https://postfun.xyz/api/auth/login', {
method: 'post',
headers: {
'content-type': 'application/json'
},
body: json.stringify(signedevent)
});

if (loginresponse.ok) {
const { token } = await loginresponse.json();
console.log("login successful! received jwt:", token);
// store this jwt (e.g., in localstorage or an authentication context)
// for all subsequent authenticated api requests.
} else {
console.error("backend login failed:", await loginresponse.text());
// handle backend authentication errors
}

6. using the jwt for authenticated requests

once you have the jwt, attach it to the authorization header of all subsequent authenticated api calls to the postfun backend.

// example: fetch user's private dashboard data
const jwt = localstorage.getitem('postfun_jwt'); // assuming you stored it
const mydataresponse = await fetch('https://postfun.xyz/api/me', {
headers: {
'authorization': `bearer ${jwt}`
}
});

if (mydataresponse.ok) {
const userdata = await mydataresponse.json();
console.log("user data:", userdata);
} else {
console.error("failed to fetch user data.");
}

security best practices for developers

when building with nip-07 and postfun authentication, always keep these critical security principles in mind:

  • never request the user's nsec directly: the entire purpose of nip-07 is to abstract this away. if your application asks for an nsec, it's a red flag.
  • always use unique challenges: each login attempt (or any signed action) should use a unique, single-use challenge generated by your backend. this prevents replay attacks.
  • verify signatures on the backend: never trust a signed event received directly from the frontend. always send the full signed event to your backend for cryptographic verification before granting access or performing any action.
  • handle user cancellations: users can always decline a signing request from the extension. your code must gracefully handle this scenario.
  • jwt security: store jwts securely (e.g., in localstorage for convenience, but be aware of xss risks; httponly cookies from a backend are more secure for server-rendered apps). ensure your jwts have short expiry times.
  • rate limiting: implement rate limiting on your api endpoints to prevent abuse.

by following these guidelines, you can build secure and robust applications that integrate seamlessly with the postfun ecosystem, empowering users with true self-custody and decentralized identity.