Skip to main content

Pi Network Authentication Guide

Welcome to the implementation guide for Pi Network Authentication. This page covers the Pi SDK (window.Pi) delivered from https://sdk.minepi.com/pi-sdk.js.

Overview​

Authentication is the entry point for any Pi App. It allows you to:

  • Verify the user’s identity via the Pi Browser.
  • Obtain a unique UID and Username.
  • Secure an AccessToken for server-side validation.

Setup & Installation​

Include the Pi SDK​

Ensure the Pi SDK is available in your HTML <head>. This provides the bridge to the Pi Browser.

<head>
<!-- ... other <head> content (meta, title, styles, etc.) ... -->
<script src="https://sdk.minepi.com/pi-sdk.js"></script>
</head>

Initialize it once on page load, before any other Pi SDK calls:

Pi.init({ version: "2.0", sandbox: true }); // use sandbox: false in production

See the Client SDK reference for Pi.init options.

Backend verification​

Authentication is only complete once verified on the server. Send the accessToken returned by Pi.authenticate() to your backend (Node.js, Python, Go, etc.).

  • Token Validation: Your backend should call the Pi Platform API /me endpoint with Authorization: Bearer <accessToken>.
  • Session Management: Once the Pi API confirms the UID, your server can issue a JWT or session cookie for your specific app.

You can wrap window.Pi in your frontend framework’s state layer (React context/hooks, Vue/Svelte stores, Angular services). Always call Pi.authenticate() — do not invent an alternate auth path.

Implementation: The PiService Pattern​

Wrapping your authentication logic in a service class keeps it in one place, makes it easier to test, and stops auth calls from being scattered across components.

The Service Wrapper​

/**
* PiService handles all communication with the Pi Network.
* This abstraction allows for easier testing and future feature expansion.
*/
class PiService {
constructor() {
this.user = null;
}

/**
* Authenticates the user and requests specific data scopes.
* @param {string[]} scopes - Defaults to ['username']
*/
async login(scopes = ['username']) {
try {
const auth = await window.Pi.authenticate(scopes, this.onIncompletePaymentFound);

this.user = auth.user;
console.log(`Authenticated as ${this.user.username}`);

return {
success: true,
accessToken: auth.accessToken,
user: auth.user
};
} catch (error) {
console.error("Authentication Error:", error);
return { success: false, error: error.message };
}
}

/**
* Required callback for Pi SDK.
* Handles payments that were interrupted before completion.
*/
onIncompletePaymentFound(payment) {
console.warn("Incomplete payment found:", payment.identifier);
// Logic to resolve this on the backend should be added here.
}
}

export const piService = new PiService();

Integration Logic​

To add login to a page:

  1. Event Binding: Attach a listener to a “Login” button.
  2. Call Service: Execute piService.login().
  3. UI Update: Transition the interface from “Guest” to “User” state using the returned user.username.
  4. Token Exchange: Send the accessToken to your application backend for verification against the Pi API.

Example UI Trigger​

async function handleLogin() {
const result = await piService.login(['username', 'payments']);

if (result.success) {
document.getElementById('user-profile').innerText = `Hello, ${result.user.username}`;
} else {
alert("Could not sign in: " + result.error);
}
}

Best practices​

  • Use async/await with Pi.authenticate() rather than nesting callbacks.
  • Wrap authentication in try/catch so a user cancelling the flow doesn't surface as an unhandled rejection.
  • Request the narrowest scopes you need. Only ask for payments when your app actually charges the user — over-requesting costs you trust.
  • Enable sandbox mode in development if you are testing outside the Pi Browser.
  • Treat your server as the source of truth. Never authorize off client-side user data; verify the accessToken against /me on your backend.