Overview
The YouVersion Platform SDK implements OAuth 2.0 with PKCE (Proof Key for Code Exchange) for secure user authentication. This flow is designed for public clients (like browser-based apps) where client secrets cannot be securely stored.
PKCE enhances security by using dynamically generated code verifiers and challenges, making authorization code interception attacks ineffective.
Authentication Flow
The OAuth 2.0 PKCE flow consists of several steps:
Quick Start
Wrap your app with YouVersionProvider and enable authentication:
import { YouVersionProvider } from '@youversion/platform-react-hooks' ;
function App () {
return (
< YouVersionProvider
appKey = "your-app-key"
includeAuth = { true }
authRedirectUrl = "https://myapp.com/callback"
theme = "light"
>
< MyApp />
</ YouVersionProvider >
);
}
The authRedirectUrl must match the redirect URI configured in your YouVersion app settings.
Step 2: Implement Sign-In
Use the useYVAuth hook to trigger authentication:
import { useYVAuth } from '@youversion/platform-react-hooks' ;
function SignInPage () {
const { auth , signIn } = useYVAuth ();
const handleSignIn = async () => {
try {
await signIn ();
// User will be redirected to YouVersion
} catch ( error ) {
console . error ( 'Sign in failed:' , error );
}
};
if ( auth . isAuthenticated ) {
return < div > Already signed in! </ div > ;
}
return (
< button onClick = { handleSignIn } disabled = { auth . isLoading } >
{ auth . isLoading ? 'Signing in...' : 'Sign In with YouVersion' }
</ button >
);
}
Step 3: Handle OAuth Callback
Create a callback page to process the authorization code:
import { useYVAuth } from '@youversion/platform-react-hooks' ;
import { useEffect , useState } from 'react' ;
import { useNavigate } from 'react-router-dom' ;
function CallbackPage () {
const { processCallback } = useYVAuth ();
const [ isProcessing , setIsProcessing ] = useState ( true );
const navigate = useNavigate ();
useEffect (() => {
processCallback ()
. then ( result => {
if ( result ) {
console . log ( 'Authentication successful:' , result . name );
// Redirect to app
navigate ( '/home' );
}
})
. catch ( error => {
console . error ( 'Authentication failed:' , error );
navigate ( '/signin?error=auth_failed' );
})
. finally (() => {
setIsProcessing ( false );
});
}, [ processCallback , navigate ]);
return (
< div >
{ isProcessing ? 'Processing authentication...' : 'Authentication complete' }
</ div >
);
}
Once authenticated, access user info throughout your app:
import { useYVAuth } from '@youversion/platform-react-hooks' ;
function UserProfile () {
const { auth , userInfo , signOut } = useYVAuth ();
if ( ! auth . isAuthenticated || ! userInfo ) {
return < div > Not signed in </ div > ;
}
return (
< div >
< h2 > Welcome, { userInfo . name } ! </ h2 >
< p > Email: { userInfo . email } </ p >
< button onClick = { signOut } > Sign Out </ button >
</ div >
);
}
Core Layer Implementation
If you’re not using React, you can use the core layer directly:
import {
SignInWithYouVersionPKCEAuthorizationRequestBuilder ,
SessionStorageStrategy
} from '@youversion/platform-core' ;
const storage = new SessionStorageStrategy ();
// Build authorization request
const { url , parameters } = await SignInWithYouVersionPKCEAuthorizationRequestBuilder . make (
'your-app-key' ,
new URL ( 'https://myapp.com/callback' ),
[ 'openid' , 'profile' , 'email' ] // optional scopes
);
// Store PKCE parameters for callback
storage . setItem ( 'pkce_code_verifier' , parameters . codeVerifier );
storage . setItem ( 'pkce_state' , parameters . state );
storage . setItem ( 'pkce_nonce' , parameters . nonce );
// Redirect user to authorization URL
window . location . href = url . toString ();
Storage Strategies
The SDK provides flexible storage strategies for managing authentication tokens:
SessionStorage (Default)
Stores tokens in browser sessionStorage. Tokens persist during the browser session but are cleared when the tab/window is closed.
import { SessionStorageStrategy } from '@youversion/platform-core' ;
const storage = new SessionStorageStrategy ();
storage . setItem ( 'auth_token' , 'token_value' );
const token = storage . getItem ( 'auth_token' );
Security Warning: SessionStorage is vulnerable to XSS attacks. If an attacker can inject JavaScript into your application, they can access all sessionStorage values.For production applications handling sensitive tokens, consider:
Using secure, HTTP-only cookies
Storing tokens in memory only (requires re-auth on page reload)
Implementing a custom storage backend with additional security measures
MemoryStorage
Stores tokens in memory only. Provides better XSS protection but requires re-authentication on page reload.
import { MemoryStorageStrategy } from '@youversion/platform-core' ;
const storage = new MemoryStorageStrategy ();
storage . setItem ( 'auth_token' , 'token_value' );
const token = storage . getItem ( 'auth_token' );
// Token is cleared on page refresh
Custom Storage
Implement your own storage strategy by implementing the StorageStrategy interface:
import type { StorageStrategy } from '@youversion/platform-core' ;
class CustomStorageStrategy implements StorageStrategy {
setItem ( key : string , value : string ) : void {
// Your custom implementation
// Example: Encrypt before storing
}
getItem ( key : string ) : string | null {
// Your custom implementation
// Example: Decrypt after retrieving
return null ;
}
removeItem ( key : string ) : void {
// Your custom implementation
}
clear () : void {
// Your custom implementation
}
}
PKCE Implementation Details
The SDK automatically handles PKCE parameter generation:
Code Verifier
Code Challenge
State
Nonce
A cryptographically random string (32 bytes, base64url-encoded): // Automatically generated by the SDK
const codeVerifier = randomURLSafeString ( 32 );
// Example: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
SHA-256 hash of the code verifier (base64url-encoded): // Automatically generated by the SDK
const challenge = await codeChallenge ( verifier );
// Uses S256 method (SHA-256)
Random string to prevent CSRF attacks: // Automatically generated by the SDK
const state = randomURLSafeString ( 24 );
// Must be verified on callback
Random value to prevent replay attacks: // Automatically generated by the SDK
const nonce = randomURLSafeString ( 24 );
// Validated in ID token
After successful authentication, the SDK decodes the ID token (JWT) to extract user information:
export interface YouVersionUserInfo {
sub : string ; // User ID
name ?: string ; // Full name
email ?: string ; // Email address
email_verified ?: boolean ;
picture ?: string ; // Profile picture URL
locale ?: string ; // User's locale (e.g., "en-US")
// ... additional OIDC claims
}
Access user info through the useYVAuth hook:
const { userInfo } = useYVAuth ();
console . log ( userInfo ?. sub ); // User ID
console . log ( userInfo ?. name ); // Display name
console . log ( userInfo ?. email ); // Email
Authentication State
The auth object provides complete authentication state:
interface AuthenticationState {
isAuthenticated : boolean ; // True if user has valid tokens
isLoading : boolean ; // True during auth flow
accessToken : string | null ; // OAuth access token
idToken : string | null ; // OIDC ID token (contains user info)
error : Error | null ; // Authentication error if any
result : SignInWithYouVersionResult | null ; // Auth result
}
Example usage:
function ProtectedRoute ({ children }) {
const { auth } = useYVAuth ();
if ( auth . isLoading ) {
return < LoadingSpinner /> ;
}
if ( ! auth . isAuthenticated ) {
return < Navigate to = "/signin" /> ;
}
if ( auth . error ) {
return < ErrorMessage error = { auth . error } /> ;
}
return children ;
}
Scopes and Permissions
Request specific permissions using OAuth scopes:
const { signIn } = useYVAuth ();
// Request specific scopes
await signIn ({
redirectUrl: 'https://myapp.com/callback' ,
scopes: [ 'openid' , 'profile' , 'email' , 'highlights' ]
});
Available scopes:
Scope Description openidRequired - enables OIDC authentication profileAccess to user’s profile (name, picture) emailAccess to user’s email address highlightsAccess to user’s Bible highlights
The openid scope is automatically included even if not specified.
Token Management
The SDK automatically manages token refresh and expiration:
import { YouVersionAPIUsers , YouVersionPlatformConfiguration } from '@youversion/platform-core' ;
// Check if token needs refresh
if ( YouVersionPlatformConfiguration . refreshToken ) {
await YouVersionAPIUsers . refreshTokenIfNeeded ();
}
// Access current tokens
const accessToken = YouVersionPlatformConfiguration . accessToken ;
const idToken = YouVersionPlatformConfiguration . idToken ;
Sign Out
Clear authentication state and tokens:
import { useYVAuth } from '@youversion/platform-react-hooks' ;
function SignOutButton () {
const { signOut } = useYVAuth ();
return (
< button onClick = { signOut } >
Sign Out
</ button >
);
}
The signOut function:
Clears all stored tokens
Resets user info state
Clears storage (session/memory)
The UI package provides a ready-to-use authentication button:
import { YouVersionAuthButton } from '@youversion/platform-react-ui' ;
function App () {
return (
< YouVersionAuthButton
redirectUrl = "https://myapp.com/callback"
onAuthError = { ( error ) => console . error ( 'Auth error:' , error ) }
mode = "auto" // "signIn" | "signOut" | "auto"
size = "default" // "default" | "short" | "icon"
variant = "default" // "default" | "outline"
radius = "rounded" // "rounded" | "rectangular"
background = "light" // "light" | "dark"
/>
);
}
The button automatically switches between “Sign In” and “Sign Out” when mode="auto".
Security Best Practices
Important Security Considerations:
HTTPS Required - Always use HTTPS in production
Validate State - Always verify the state parameter on callback
Secure Storage - Consider HTTP-only cookies for token storage
Token Expiration - Implement proper token refresh logic
XSS Prevention - Sanitize user input and use Content Security Policy
Redirect URI Validation - Ensure redirect URIs match exactly
Troubleshooting
State Mismatch Error
Problem: State mismatch - potential CSRF attack
Solution: Ensure the state parameter from the callback matches the stored state. This can happen if:
Browser storage was cleared between redirect
User opened the callback URL directly
Multiple auth flows are running simultaneously
Code Verifier Not Found
Problem: Code verifier not found
Solution: The PKCE code verifier was not stored or was cleared. Ensure:
Storage is working correctly
User hasn’t cleared cookies/storage
Code verifier is stored before redirect
Token Expired
Problem: API calls fail with 401 Unauthorized
Solution: Implement automatic token refresh:
import { YouVersionAPIUsers } from '@youversion/platform-core' ;
try {
await YouVersionAPIUsers . refreshTokenIfNeeded ();
// Retry the API call
} catch ( error ) {
// Token refresh failed - re-authenticate
signOut ();
}
Next Steps
Architecture Learn about the three-layer SDK architecture
Data Fetching Fetch Bible data with authentication