Authentication
Understand user authentication and profile management in Stacks apps
Overview
Stacks authentication provides decentralized identity management, allowing users to control their data and authenticate without passwords. When users connect their wallet, they're actually creating an authenticated session with your app using their decentralized identifier (DID).
Authentication flow
User session management
The UserSession
object manages the authentication state throughout your app's lifecycle:
import { UserSession, AppConfig } from '@stacks/connect';// Initialize with required permissionsconst appConfig = new AppConfig(['store_write', 'publish_data']);const userSession = new UserSession({ appConfig });// Check authentication statusif (userSession.isUserSignedIn()) {// User is authenticatedconst userData = userSession.loadUserData();} else if (userSession.isSignInPending()) {// Authentication in progressuserSession.handlePendingSignIn().then(userData => {// Handle successful sign in});}
User data structure
When authenticated, you can access comprehensive user data:
interface UserData {username?: string; // BNS username if availableemail?: string; // Email if permission grantedprofile: {stxAddress: {mainnet: string; // Mainnet STX addresstestnet: string; // Testnet STX address};};decentralizedID: string; // User's DIDidentityAddress: string; // Bitcoin address for identityappPrivateKey: string; // App-specific private keyhubUrl: string; // Gaia hub URLassociationToken: string; // Gaia association token}
Access user data after authentication:
const userData = userSession.loadUserData();console.log('Username:', userData.username);console.log('Mainnet address:', userData.profile.stxAddress.mainnet);console.log('Testnet address:', userData.profile.stxAddress.testnet);console.log('DID:', userData.decentralizedID);
Handling authentication states
Implement proper state management for authentication:
import { useState, useEffect } from 'react';function useAuth() {const [authState, setAuthState] = useState<'loading' | 'authenticated' | 'unauthenticated'>('loading');const [userData, setUserData] = useState<any>(null);useEffect(() => {if (userSession.isSignInPending()) {userSession.handlePendingSignIn().then(userData => {setUserData(userData);setAuthState('authenticated');});} else if (userSession.isUserSignedIn()) {setUserData(userSession.loadUserData());setAuthState('authenticated');} else {setAuthState('unauthenticated');}}, []);return { authState, userData };}
Authentication redirect flow
For apps that use redirect-based authentication:
// In your auth handler componentuseEffect(() => {if (userSession.isSignInPending()) {userSession.handlePendingSignIn().then(userData => {// Clear URL parameters after handlingwindow.history.replaceState({}, document.title, window.location.pathname);// Update app statesetUser(userData);}).catch(error => {console.error('Sign in failed:', error);});}}, []);
Profile updates
Users can update their profile data, which is stored in Gaia:
async function updateProfile(updates: any) {const userData = userSession.loadUserData();// Update profile dataconst newProfile = {...userData.profile,...updates};// Save to Gaia storageawait userSession.putFile('profile.json',JSON.stringify(newProfile),{ encrypt: false } // Public profile data);}
Multi-account handling
Some wallets support multiple accounts. Handle account switches:
// Listen for account changeswindow.addEventListener('accountsChanged', (event: any) => {const newAddress = event.detail.addresses[0];if (newAddress !== currentAddress) {// Handle account switchconsole.log('Account switched to:', newAddress);// Re-authenticate or update UIhandleAccountSwitch(newAddress);}});
Session persistence
User sessions persist across page reloads. Configure session behavior:
const appConfig = new AppConfig(['store_write', 'publish_data'],'https://myapp.com' // App domain for session management);const userSession = new UserSession({appConfig,sessionOptions: {// Custom session storage (default: localStorage)sessionStore: new LocalStorageStore(),}});
Sign out implementation
Properly handle user sign out:
function signOut() {// Clear user sessionuserSession.signUserOut();// Clear any app-specific datalocalStorage.removeItem('appData');sessionStorage.clear();// Redirect to home or login pagewindow.location.href = '/';}
Authentication guards
Protect routes or components that require authentication:
function AuthGuard({ children }: { children: React.ReactNode }) {const { authState } = useAuth();if (authState === 'loading') {return <LoadingSpinner />;}if (authState === 'unauthenticated') {return <Navigate to="/login" />;}return <>{children}</>;}// Usage<AuthGuard><ProtectedComponent /></AuthGuard>
Advanced authentication options
Customize the authentication experience:
import { showConnect, AuthOptions } from '@stacks/connect';const authOptions: AuthOptions = {appDetails: {name: 'My App',icon: '/logo.png',},redirectTo: '/', // Redirect after authmanifestPath: '/manifest.json', // App manifest locationsendToSignIn: false, // Skip wallet selectionuserSession,onFinish: ({ userSession }) => {// Access the authenticated sessionconst userData = userSession.loadUserData();console.log('Authenticated:', userData);},onCancel: () => {console.log('Authentication cancelled');},};showConnect(authOptions);
Security considerations
- Never expose private keys: The
appPrivateKey
should only be used for Gaia operations - Validate addresses: Always validate user addresses before transactions
- HTTPS only: Authentication requires HTTPS in production
- Session timeouts: Consider implementing session timeouts for sensitive apps
Common patterns
Remember me functionality
// Store preferencelocalStorage.setItem('autoConnect', 'true');// Check on app loaduseEffect(() => {const shouldAutoConnect = localStorage.getItem('autoConnect') === 'true';if (shouldAutoConnect && !userSession.isUserSignedIn()) {// Attempt to restore sessionattemptSilentAuth();}}, []);
Guest mode with optional auth
function App() {const { authState, userData } = useAuth();return (<div>{authState === 'authenticated' ? (<AuthenticatedApp user={userData} />) : (<GuestApp onConnect={connectWallet} />)}</div>);}