One-click login that removes password management, covers 95%+ of the target customer base, and links portal users directly to their CRM record.
Password-based registration for a customer portal is a friction source and a liability. Customers forget passwords, use weak ones, and abandon registration flows that ask them to create yet another account. OAuth — letting customers log in with an account they already have — removes the friction and the password storage problem simultaneously. Three providers cover 95%+ of the target customer base: Google, Apple, and Microsoft.
The portal needed login without creating a new password management problem. The customers are not technical — they’re buyers and consignors. A registration flow that asks for email + password + confirmation + email verification would have sub-20% completion. The alternative: one click with an account they already trust.
All three providers use the same basic OAuth 2.0 flow:
WordPress creates a user with wp_insert_user() on first login and calls wp_set_auth_cookie() to establish the session. Subsequent logins find the existing user by the stored provider user ID.
function [client]_google_login_url(): string {
$state = wp_create_nonce( '[client]_google_oauth' );
set_transient( 'google_oauth_state_' . $state, 1, 300 ); // 5-min window
$params = http_build_query( [
'client_id' => [client]_get_setting( 'google_client_id' ),
'redirect_uri' => [client]_oauth_callback_url( 'google' ),
'response_type' => 'code',
'scope' => 'openid email profile',
'state' => $state,
] );
return 'https://accounts.google.com/o/oauth2/v2/auth?' . $params;
}
function [client]_handle_google_callback( string $code, string $state ): int|false {
// Verify state token
if ( ! get_transient( 'google_oauth_state_' . $state ) ) {
return false;
}
delete_transient( 'google_oauth_state_' . $state );
// Exchange code for token
$token_response = wp_remote_post( 'https://oauth2.googleapis.com/token', [
'body' => [
'code' => $code,
'client_id' => [client]_get_setting( 'google_client_id' ),
'client_secret' => [client]_get_setting( 'google_client_secret' ),
'redirect_uri' => [client]_oauth_callback_url( 'google' ),
'grant_type' => 'authorization_code',
],
] );
if ( is_wp_error( $token_response ) ) return false;
$tokens = json_decode( wp_remote_retrieve_body( $token_response ), true );
$id_token = $tokens['id_token'] ?? '';
// Decode JWT payload (middle segment, base64)
$payload = json_decode( base64_decode( explode( '.', $id_token )[1] ), true );
return [client]_find_or_create_portal_user(
'google',
$payload['sub'],
sanitize_email( $payload['email'] ),
sanitize_text_field( $payload['name'] ?? '' )
);
}
Apple has two notable differences from Google and Microsoft. First, Apple only sends the user’s name on the first authorization — subsequent logins send the user ID and email only. Store the name on first login; you won’t see it again. Second, Apple’s JWT verification requires validating the signature against Apple’s public key.
function [client]_handle_apple_callback( string $code, string $state, array $user_data = [] ): int|false {
// $user_data is only populated on FIRST login — Apple omits it on subsequent logins
$first_login = ! empty( $user_data );
// ... token exchange omitted for brevity ...
// Store name ONLY if present (first login)
$display_name = '';
if ( $first_login && ! empty( $user_data['name'] ) ) {
$display_name = trim( $user_data['name']['firstName'] . ' ' . $user_data['name']['lastName'] );
}
return [client]_find_or_create_portal_user(
'apple',
$apple_user_id,
$email,
$display_name
);
}
Microsoft uses the same OAuth 2.0 pattern but requires registering an app in Azure Active Directory. The token endpoint is tenant-specific. Use common as the tenant to accept both personal Microsoft accounts and organizational accounts: https://login.microsoftonline.com/common/oauth2/v2.0/token.
function [client]_find_or_create_portal_user(
string $provider,
string $provider_user_id,
string $email,
string $display_name = ''
): int|false {
// Look for existing user by provider ID stored in user meta
$existing_users = get_users( [
'meta_key' => '[client]_oauth_' . $provider . '_id',
'meta_value' => $provider_user_id,
'number' => 1,
] );
if ( ! empty( $existing_users ) ) {
return $existing_users[0]->ID;
}
// Check if email already exists (different provider, same person)
$by_email = get_user_by( 'email', $email );
if ( $by_email ) {
update_user_meta( $by_email->ID, '[client]_oauth_' . $provider . '_id', $provider_user_id );
return $by_email->ID;
}
// Create new user with restricted portal role
$user_id = wp_insert_user( [
'user_login' => $email,
'user_email' => $email,
'display_name' => $display_name ?: $email,
'role' => '[client]_portal_customer',
'user_pass' => wp_generate_password( 32 ),
] );
if ( is_wp_error( $user_id ) ) return false;
update_user_meta( $user_id, '[client]_oauth_' . $provider . '_id', $provider_user_id );
// Link to existing CRM lead if email matches
$lead = [client]_get_lead_by_email( $email );
if ( $lead ) {
update_user_meta( $user_id, '[client]_portal_lead_id', $lead->id );
}
return $user_id;
}
The [client]_portal_customer role is a custom role with no admin capabilities. Portal users can only access portal pages — nothing in WordPress admin.
Password-based registration for a small-business customer portal produces low completion rates and ongoing password-reset support requests. OAuth offloads the credential management problem to Google/Apple/Microsoft, who are better at it, and produces a one-click login that customers actually complete.
The email-matching fallback in find_or_create_portal_user() handles the case where a customer used Google last time and tries Apple this time. Same email address → same user record, even across different providers.
All three providers were implemented. Google accounted for 68% of portal logins, Microsoft 19%, Apple 13%. The Apple implementation required the most care — the name-only-on-first-login behavior caused a bug in the first version where returning Apple users had blank display names. Fixed by storing the name on first login and not overwriting it on subsequent ones.
Every lesson stays free — no account, no paywall, no email gate, ever. But if you’d rather have this system standing on your business than wire all 48 lessons yourself, leave your email. We’ll send you a direct line to a build — and you’ll be first to hear when we add new tools to the curriculum.
None of this gates a single lesson. The curriculum was free before you got here and it stays that way.
You came here to understand the system, and now you do. If you’d rather have it standing on your business than spend the next three months wiring it yourself, GAP Concierge is the same architecture from these lessons — a white-label AI agent that knows your catalog and captures your leads — set up for you, from $97/mo.
See GAP Concierge →