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:
- Workspace admin removes employee.
- Within minutes, Workspace's SCIM call to your SCIM endpoint deactivates them.
- 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_accountUseful 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
- Sign in with consumer @gmail.com → rejected with
gmail_not_allowed. - Sign in with @your-corp.com → success.
- Sign in with @anothercorp.com (Workspace) → rejected with
wrong_domain. - Sign in with a recently-created throwaway @your-corp.com test account → success (good, it's in your IdP).
- Sign in after removing the test account from Workspace → fails (good).