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.