Olympus Docs
CookbookSocial login

Sign in with Google (high-assurance)

Locking down Google OIDC for enterprise

The basic Add social provider, Google gets you running. For higher-assurance setups (enterprise, regulated industries), you'll want extra hardening.

Threats

Default Google OIDC accepts any Google account, including:

  • Consumer @gmail.com.
  • @yourcorp.com, but ALSO @anycorp.com that happens to use Google.
  • Recently created throwaway Google accounts.
  • Accounts where the user's email is unverified.

For B2B, you usually want only your organization's accounts.

Mitigation 1: hd claim check

Google issues an hd claim (hosted domain) only for Google Workspace accounts. Consumer @gmail.com accounts don't have it.

// oidc-google.jsonnet
local claims = std.extVar('claims');
if !std.objectHas(claims, 'hd') then 
  error 'gmail_not_allowed'
else if claims.hd != 'your-corp.com' && claims.hd != 'subsidiary.com' then 
  error 'wrong_domain'
else {
  identity: {
    traits: {
      email: claims.email,
      first_name: claims.given_name,
      last_name: claims.family_name,
    },
  },
}

Now only @your-corp.com and @subsidiary.com Workspace users can sign in.

Mitigation 2: Restricted client (Google Workspace)

If you have admin in Google Workspace, restrict the OAuth client to a domain:

Google Cloud Console → APIs & Services → OAuth consent screen → Internal (Workspace).

Only your domain's users can authorize.

Note: this requires the Workspace admin's cooperation. For multi-tenant SaaS where customers each have their own Workspace, this doesn't apply.

Mitigation 3: Require email_verified

if !claims.email_verified then 
  error 'email_not_verified'
else { ... }

Workspace accounts are always email-verified. Consumer Gmail isn't always. Belt + suspenders.

Mitigation 4: Lock to specific OAuth client

When you create the Google OAuth client, you set authorized JavaScript origins and redirect URIs. Validate at runtime:

// claims.aud is your Google OAuth client_id
if claims.aud != std.extVar('expected_aud') then
  error 'invalid_audience'
else { ... }

Mitigation 5: Linkability

Prevent account hijacking via email reuse:

Scenario: attacker gets a corporate email like victim@your-corp.com (e.g., former employee email reactivated). They Google-sign-in → Kratos matches by email → attacker is in victim's account.

Mitigation: account linking requires explicit confirmation (user types password, then links Google). Don't auto-link by email.

See Cookbook, Account linking strategies.

Mitigation 6: Audit Workspace policies

Even with hd check, a user with your-corp.com email might be:

  • A contractor with limited access.
  • Someone who left and hasn't been removed.

Have Workspace admin set up:

  • Single Sign-On for SaaS.
  • Auto-deprovisioning when user is removed.
  • 2FA required for all employees.

Olympus then trusts the IdP, but IdP must be trustworthy.

SCIM for deprovisioning

If using Google Workspace as IdP for Olympus:

  1. Workspace admin removes employee.
  2. Within minutes, Workspace's SCIM call to your SCIM endpoint deactivates them.
  3. They can't log into Olympus or any Olympus-protected app.

See SCIM endpoint.

Disable password method

For high-assurance: only allow Google sign-in. Disable password:

# kratos.yml
selfservice:
  methods:
    password:
      enabled: false  # no password registration / login
    oidc:
      enabled: true
      config:
        providers: [ google ]

Then there's no password to brute-force, leak, or phish. Users must come through Google → all IdP policies (MFA, device check) apply.

Force account selection

By default, Google might auto-sign-in if the user has a single session. To force account picker:

- id: google
  provider: google
  scope: [openid, profile, email]
  additional_id_token_audiences: []
  requested_claims:
    id_token:
      hd: { essential: true }
  prompt: select_account

Useful when users have multiple Google accounts.

Conditional sign-in (existing browser sessions)

For UX, you can show "Sign in with Google" with prompt=none first, if user already has a Google session, immediate sign-in. If not, fall back to the click-button flow.

const url = new URL("/oauth2/auth/google", KRATOS_PUBLIC);
url.searchParams.set("prompt", "none");
window.location.assign(url);

If the iframe-based silent flow fails, redirect to the normal flow.

Test plan

  1. Sign in with consumer @gmail.com → rejected with gmail_not_allowed.
  2. Sign in with @your-corp.com → success.
  3. Sign in with @anothercorp.com (Workspace) → rejected with wrong_domain.
  4. Sign in with a recently-created throwaway @your-corp.com test account → success (good, it's in your IdP).
  5. Sign in after removing the test account from Workspace → fails (good).

On this page