To use YouVersion sign-in from an app or web page, the easiest way to do so is to use our SDKs instead of using the information on this page. This guide walks through the YouVersion OAuth authorization code flow using PKCE, which can be a challenging task and is intended for developing new SDKs or situations where our existing SDKs don't support your tech stack or feature needs.
Breaking change (July 20–21, 2026): The first callback after consent no longer carries identity parameters (
yvp_id,user_name,user_email,profile_picture). Identity is now bound server-side and the browser-facing callback URL carries onlystate(plus an optionalgranted_permissions). Direct (no-SDK) integrations that read identity from that first callback, or that gated the replay to/auth/callbackon those parameters, must instead replaystatealone. See Auth Call 2: /callback below for the corrected two-hop flow.
OAuth scopes vs. requested permissions
Two different concepts control what your app receives, and they are not interchangeable:
- OAuth
scopeselects OpenID Connect identity claims. The only supported scope values areopenid,profile, andemail.openidis required. As of the July 2026 change,/auth/authorizerejects any unsupported scope value with a400 invalid_scopeerror rather than silently ignoring it. requested_permissions[]requests access to a user's YouVersion data. The only supported permission ishighlights. Notes and bookmarks are not supported and must not be requested.
Never pass a permission key (e.g. highlights) as an OAuth scope, and never pass a scope value as a requested_permissions[] entry.
Pre-auth: Retrieve your app_key
First, create your developer account and register your application at platform.YouVersion.com to obtain an App Key.
- Create a new App
- Set the
callback url - Upon creation, note the
app_key. This will be the oauthclient_id
Auth Call 1: /authorize
This initiates the auth flow for the end user, who will be redirected to login.youversion.com and, after signing in there, be presented with scopes ("YouVersion wants to share your email with App XYZ").
ANDROID DEVS - Android requires in-browser "user interaction" for authorization to complete. You will need to additionally add param require_user_interaction=true to this call, which will prompt the user to click a continuation button after each login.
Endpoint URL
Code
Query Parameters
| Parameter | Description |
|---|---|
response_type | code |
client_id | Your app's client ID from the Platform Portal (the app_key) |
redirect_uri | Your app's callback URL (must match the one registered in the Platform Portal) |
scope | Space-separated list of requested scopes. Supported values: openid, profile, email (openid required). Any other value is rejected with 400 invalid_scope. |
nonce | Random string for replay protection (generate a unique value per request) |
state | Unique random string for CSRF protection, required exactly once. Maximum 256 bytes after URL decoding and UTF-8 encoding. Save the exact value to verify it on every callback. |
code_challenge | Base64 URL-encoded SHA256 hash of the code_verifier (for PKCE) |
code_challenge_method | S256 (indicates SHA256 hashing) |
requested_permissions[] | Optional. Data-access permissions to request, distinct from scope. Only highlights is supported (repeat the param for each, e.g. requested_permissions[]=highlights). |
require_user_interaction | true Optional param for Android and any platform that requires user interaction to continue sign-in flows |
The state limit counts UTF-8 bytes, not characters or the length of the encoded URL. For example, 128 é characters occupy 256 bytes. The server preserves accepted state exactly. A missing, repeated, or oversized state returns 400 invalid_request before a sign-in session is created.
Example Request URL
Code
Redirect back to client
After the user successfully authenticates and grants consent, they will be redirected (303) back to your App's callback URL. This first callback is state-only — it does not carry any identity parameters. Identity is bound server-side and retrieved during the next hop, so you do not receive (or need) yvp_id, user_name, user_email, or profile_picture here.
| Parameter | Description |
|---|---|
state | The same state value you provided in the original request (for CSRF validation) |
granted_permissions | Optional. Comma-separated list of granted permission keys (e.g. highlights). Present only when your request included requested_permissions[]; may be empty if none were granted. |
Example Redirect URL
Code
With a granted permission:
Code
Migration note: Before July 20–21, 2026 this callback carried
yvp_id,user_name,user_email, andprofile_picture. It no longer does. Validatestateagainst the value you generated in Auth Call 1, then replaystatealone to/auth/callbackas shown below.
Auth Call 2: /callback
After validating state from the first callback, your client app replays that state value alone to the /callback endpoint. The server sources the user's identity from the server-side entry it stored during consent (keyed by state), so no identity parameters are sent or needed. This hop mints the authorization code for the final step.
Endpoint URL
Code
Query Parameters
| Parameter | Description |
|---|---|
state | The same state value from the first callback (identity is resolved server-side from this value) |
Example Request URL
Code
Response
The server will respond with a redirect (302) to your callback URL with the authorization code:
Code
Response Parameters:
code: The authorization code to exchange for tokensstate: Your originalstatevalue for validationgranted_permissions(optional): Comma-separated granted permission keys, present only whenrequested_permissions[]was included in Auth Call 1
Note: No
scopeparameter is returned on this redirect. The granted OAuth scopes are available on the token response (Auth Call 3) and inside the issued tokens.
Auth Call 3: /token
Exchange the authorization code returned by Auth Call 2 for access tokens. This is a POST request made from your backend or from a public client. Public clients use PKCE and do not send a client secret.
Endpoint URL
Code
Request Body Parameters
| Parameter | Description |
|---|---|
grant_type | authorization_code (OAuth 2.0 grant type) |
code | The authorization code returned by Auth Call 2 |
redirect_uri | Must match the redirect_uri sent to Auth Call 1 |
client_id | Your app's client ID (the app_key) |
code_verifier | The original PKCE code verifier (before hashing for code_challenge) |
Example Request
Code
Response
The server responds with a JSON object containing the access tokens:
Code
Response Fields:
access_token: The OAuth 2.0 access token (JWT - use in API requests withAuthorization: Bearerheader)token_type: AlwaysBearerexpires_in: Token lifetime in seconds (typically 3599 = ~1 hour)refresh_token: Token to obtain a new access token when it expiresid_token: OpenID Connect ID token (JWT containing user claims like email, name, etc.)scope: The granted scopes
Note: Both
access_tokenandid_tokenare JSON Web Tokens (JWTs). Decoding a JWT does not verify it. Use the verifiedid_tokenfor user identity and theaccess_tokenas the bearer credential for API requests.
Copy-paste callback handler (two-hop replay + token exchange)
This minimal, dependency-free example shows the corrected direct (no-SDK) flow for a public client. It handles the state-only first callback, replays state alone to /auth/callback, then exchanges the returned code for tokens at /auth/token using the PKCE code_verifier.
It assumes you stored the state and code_verifier you generated in Auth Call 1 (for example in sessionStorage) so you can validate the returned state and complete PKCE.
Your redirect_uri page is loaded twice: once by the state-only first callback, and again after /auth/callback redirects back with the code. The handler below branches on whether a code is present in the URL. Auth Call 2 is driven by a top-level browser navigation (not fetch), because a browser cannot read the Location header of a redirect made with fetch.
Code
Reminder: Because the first callback no longer carries identity, do not attempt to read
yvp_id,user_name,user_email, orprofile_picturefrom the callback URL. Read user identity from the verifiedid_tokenclaims after the token exchange (see below).
Post-auth: Verify the ID token and read user info
After Auth Call 3, validate the id_token before using its claims to identify the user. The access_token authorizes API requests; it is not a substitute for the ID token.
Choose a JWT verification library
Use a JWT library that supports signature and claim validation for your programming language:
- JavaScript/Node.js:
jsonwebtoken,jose - Python:
PyJWT,python-jose - Ruby:
jwt - Go:
golang-jwt/jwt - Java:
java-jwt,jjwt - .NET:
System.IdentityModel.Tokens.Jwt
OIDC metadata (issuer + JWKS)
Use these production values when validating the id_token:
| Field | Value |
|---|---|
issuer (iss) | https://api.youversion.com/auth/token |
audience (aud) | Your app's app_key (the OAuth client_id) |
jwks_uri | https://api.youversion.com/.well-known/jwks.json |
| OIDC discovery URL | https://api.youversion.com/.well-known/openid-configuration |
Validate the ID token
Configure your library to perform OpenID Connect ID-token validation:
- Require
RS256and verify the signature with the public key selected by the token header'skidfrom the trusted JWKS endpoint above. - Require
issto exactly matchhttps://api.youversion.com/auth/token, including the path. - Require
audto contain your app'sapp_key(the OAuthclient_id). If multiple audiences or anazpclaim are present, apply the additional OpenID Connect authorized-party checks. - Check
expand reject expired tokens. Validateiataccording to your application's token-age policy. - Require
nonceto match the value you generated and stored for this sign-in request.
Reject the ID token if validation fails. Do not disable issuer or audience validation to make a token pass.
ID token claims
The id_token carries user identity claims. Profile and email fields depend on the granted scopes. This illustrative payload uses synthetic values; the timestamps are examples, not a valid token:
Code
Key Claims:
| Claim | Description |
|---|---|
yvp_id | The user's unique YouVersion Platform ID - use this as the primary user identifier |
sub | Subject - also contains the user's unique ID (same as yvp_id) |
email | The user's email address |
name | The user's display name |
profile_picture | URL to the user's profile picture |
aud | Audience - your app's client ID (validates the token is for your app) |
iss | Issuer - the YouVersion API endpoint that issued the token |
exp | Expiration time (Unix timestamp) |
iat | Issued at time (Unix timestamp) |
nonce | The nonce value from your original request (for replay protection) |
jti | JWT ID - unique identifier for this token |
Best Practice: Use
yvp_idas the primary key when storing user information in your database. This ID is stable and unique for each user.
Access token usage and claims
Send the access_token to the YouVersion API as Authorization: Bearer {access_token}. The API validates the bearer credential. Use the verified ID token, rather than decoded access-token claims, to establish the user's identity in your app.
Current access tokens have an at+JWT header type, use RS256, and do not include a kid header or an aud claim. They include client_id, which identifies the requesting app but is not an audience claim. Do not apply the ID token's app-key audience rule to the access token, substitute client_id for aud, or disable audience checking in your ID-token verifier.
This illustrative access-token payload uses synthetic values:
Code
The missing aud and kid explain why a verifier configured for ID tokens can reject an access token even when its signature and expiration checks pass. Treat the access token as the API bearer credential; do not use it as a login token for your own backend.
Best Practices and Resources
Security
PKCE Implementation
- Generate secure random values: Use cryptographically secure random generators for
code_verifier(43-128 characters) - Never reuse code verifiers: Generate a new one for each authorization flow
- Store code_verifier securely: Keep it in memory or secure storage until token exchange
State and Nonce
- Always validate state: Verify the returned
statematches what you sent to prevent CSRF attacks - Use unique values: Generate a new
stateandnoncefor every authorization request - Store temporarily: Associate
statewith the user's session and validate on callback
Token Security
- Validate ID tokens: Verify the signature, issuer, audience, expiration, and nonce as described above before trusting user identity. Send access tokens only as API bearer credentials.
- Store tokens securely: Use secure storage (e.g., HTTP-only cookies, encrypted storage) - never in localStorage for web apps
- Never log tokens: Avoid logging access tokens or refresh tokens in production
Token Management
Access Token Usage
- Include in API requests: Send as
Authorization: Bearer {access_token}header - Check expiration: Tokens typically expire in 1 hour - implement refresh logic before expiration
- Handle 401 errors: When an API returns 401, refresh the token and retry
Refresh Tokens
- Store securely: Refresh tokens are long-lived and sensitive
- Implement refresh flow: Use refresh tokens to obtain new access tokens without re-authenticating the user
- Revoke on logout: Call the token revocation endpoint when users log out
User Data
User Identification
- Use
yvp_idas primary key: Always useyvp_id(not email) as the stable user identifier in your database - Don't assume email uniqueness: Users can change emails, so don't rely on email as a primary key
- Update user info on login: Refresh user profile data (name, email, picture) on each login to stay current
Privacy
- Request minimum scopes: Only request the scopes your app actually needs
- Don't share user data: Never share user data with third parties without explicit consent
Testing and Production
- Test thoroughly: Test the complete flow including error cases (denied consent, expired tokens, network failures)
Error Handling
- Handle user denial: Gracefully handle when users decline to authorize your app
- Implement retry logic: Network requests can fail - implement exponential backoff
- Log errors (not tokens): Log error messages and codes, but never log tokens or sensitive data
Resources
Documentation
- OAuth 2.0 RFC 6749 - OAuth 2.0 specification
- PKCE RFC 7636 - Proof Key for Code Exchange specification
- OpenID Connect - OpenID Connect specification
- YouVersion OIDC discovery - Production issuer and endpoint metadata
- YouVersion JWKS - Public keys for verifying
access_token/id_token - JWT.io - JWT decoder and debugger
Libraries
- JavaScript:
@auth0/auth0-spa-js,oidc-client-js - Python:
authlib,python-jose - Ruby:
omniauth-oauth2 - Go:
golang.org/x/oauth2 - Java/Kotlin:
AppAuth-Android - Swift:
AppAuth-iOS(but, using our Swift SDK would be easiest.)