Skip to content

Social Auth

Kirak’s social login is built on social-auth-core (a custom FastAPIStrategy plus an async storage backend). Every backend that ships with the installed social-auth-core is available, plus Kirak’s own (instagram, tiktok) – see the Social Providers Reference for every accepted name, or run kirak catalog --json (social_providers). Several providers expose more than one entry for different API/protocol variants, e.g. Auth0 (auth0 OAuth2 vs auth0_openidconnect OIDC) or Azure AD (azuread-oauth2, azuread-oauth2-v2, azuread-b2c-oauth2, azuread-tenant-oauth2, azuread-v2-tenant-oauth2) – pick the exact backend name that matches the credentials/protocol you were issued, not just the company name. Enabling a provider requires only a one-line kirak.json entry and the provider’s credentials in .env – no changes to kirak-core are needed.


Declare the provider’s social-core name in kirak.json under auth.social_providers:

{
"auth": {
"social_providers": ["google-oauth2", "github", "linkedin-oauth2", "discord"]
}
}

Provider names are social-core’s canonical cls.name values. Kirak reads the available backends from the installed packages at startup (social-auth-core and kirak/auth/social/backends/), so the accepted names always match the installed version. The Social Providers Reference is generated from the same source and also lists each provider’s kirak.json block, secret variable and the settings kirak.json cannot supply yet. Common names:

Provider Name to use
Google OAuth2 google-oauth2
GitHub github
Apple apple-id
Facebook facebook
Instagram instagram
LinkedIn linkedin-oauth2
Microsoft / Azure AD microsoft-graph
Discord discord
Twitter / X (OAuth1) twitter
Twitter / X (OAuth2) twitter-oauth2
Slack slack
Spotify spotify
Twitch twitch
Reddit reddit
TikTok tiktok

Kirak validates all declared names against the catalog at startup and fails fast if a name is unknown or its package is not installed.

The client ID (and redirect URI) of a provider is a setting: it goes in the auth.social block of kirak.json, under the provider’s name. The client secret is a secret and stays in the environment.

"auth": {
"social_providers": ["linkedin-oauth2", "discord"],
"social": {
"linkedin-oauth2": { "client_id": "your-client-id" },
"discord": { "client_id": "your-client-id" }
}
}
# LinkedIn
SOCIAL_AUTH_LINKEDIN_OAUTH2_SECRET=your-client-secret
# Discord
SOCIAL_AUTH_DISCORD_SECRET=your-client-secret

The secret variable is SOCIAL_AUTH_<BACKEND_NAME_UPPER>_SECRET: uppercase the name and replace hyphens with underscores.

Many backends need more than a client ID: Auth0 its tenant DOMAIN, Okta its API_URL, Cognito its POOL_DOMAIN, Keycloak its PUBLIC_KEY, SAML its service provider and identity provider settings. Put any setting the provider’s social-core backend reads in its auth.social block, as a lowercase key; values may be strings, numbers, booleans, lists or objects, and are passed to the backend as they are:

"auth": {
"social_providers": ["auth0"],
"social": {
"auth0": { "client_id": "your-client-id", "domain": "acme.eu.auth0.com" }
}
}

The settings that are secrets (client_assertion, sp_private_key, bot_token, api_key) are not accepted in kirak.json; set them in the environment as SOCIAL_AUTH_<BACKEND>_<SETTING>, e.g. SOCIAL_AUTH_AZUREAD_B2C_OAUTH2_CLIENT_ASSERTION.

Which settings a provider reads, and which of them it needs, is listed per provider in Social Providers (“Other settings”) and in kirak catalog --json (social_providers[].extra_settings). kirak validate rejects a key the provider’s backend does not read.

Kirak auto-derives the callback URL from base_url in kirak.json:

{base_url}/auth/{provider-name}/callback

{provider-name} is exactly the name you declared in social_providers in kirak.json (e.g. google-oauth2, github, linkedin-oauth2). This is the URL you must register in your provider’s OAuth app settings. Kirak always listens on /{provider-name}/callback under the auth routes’ path: /auth by default, or the auth_prefix passed to create_kirak_app() (the derived URL is then {base_url}{auth_prefix}/{provider-name}/callback).

To override the full callback URL registered with the provider (e.g. when your API runs on a different domain than base_url), set redirect_uri in the provider’s block:

"auth": {
"social": {
"google": { "redirect_uri": "https://api.example.com/auth/google-oauth2/callback" },
"linkedin-oauth2": { "redirect_uri": "https://api.example.com/auth/linkedin-oauth2/callback" }
}
}

For a backend that is not in social-core (e.g. a proprietary IdP), subclass social_core.backends.oauth.BaseOAuth2 and register it at startup:

app.auth.register_social_provider(
"company-sso",
"myapp.auth.backends.sso.CompanySSO",
)

See kirak/auth/social/backends/tiktok.py as a reference implementation.

Nothing to regenerate for Kirak itself: the backends are read from the installed package. A Kirak backend in kirak/auth/social/backends/ wins over a social-core backend of the same name (that is how instagram uses Meta’s Instagram Login). Kirak’s own repository regenerates the Social Providers Reference with python scripts/gen_reference.py; a test fails when it is out of date.

Three social-core backends need packages that pip install kirak does not bring: saml (pip install "social-auth-core[saml]"), shopify ([shopify]) and google-onetap ([google-onetap]). Startup names the command when one of them is enabled without it.

check_social_backend() tells whether a backend’s client id and secret work, without a user signing in. Pass the backend’s auth.social block and its secret environment variables by name (the catalog’s secret_env_var); nothing is read from kirak.json or the environment:

from kirak.auth.social import check_social_backend
result = await check_social_backend(
"google-oauth2",
{"client_id": "...apps.googleusercontent.com",
"redirect_uri": "https://api.example.com/auth/google-oauth2/callback"},
{"KIRAK_AUTH_GOOGLE_CLIENT_SECRET": "..."},
)
result.status # "ok" | "warning" | "unverified" | "failed"
result.code # e.g. "credentials_invalid", "redirect_uri_mismatch"; None when ok

For OAuth 2 and OpenID Connect backends it sends the backend’s own token request, built as login builds it, with an authorization code no provider issued. The provider refuses the code (invalid_grant) only after accepting the client id and secret, and refuses a wrong client (invalid_client) or redirect URI (redirect_uri_mismatch) with its own answer. An answer the check cannot classify is unverified, never ok. Facebook and TikTok get a client_credentials token instead; OAuth 1 backends ask for a request token. SAML and OpenID 2 backends get the offline checks only (secret set, Apple’s private key parses, redirect_uri absolute). Pass the redirect_uri login uses: without it a redirect mismatch cannot be told apart from a wrong setting. Each check leaves one refused token request in the provider’s logs. live=False runs only the offline checks. The codes are listed in Problem codes (those marked provider check()).


GET /auth/{provider}/login

Optional query parameters:

Parameter Description
platform Client platform: ios, android, web
flow Auth intent: signup, login
device_token Push notification device token (stored after registration)

Example:

Terminal window
GET /auth/google-oauth2/login?platform=web&flow=signup

Kirak builds an OAuth state token (HMAC-signed, short-lived) encoding these parameters, then redirects the browser to the provider’s authorization URL.

GET /auth/{provider}/callback?code=...&state=...

Kirak:

  1. Verifies the HMAC state token (and its expiry – state tokens are valid for 10 minutes)
  2. Exchanges the code for provider access + refresh tokens
  3. Fetches the user’s profile from the provider
  4. Looks up an existing auth_social record for this provider + uid
  5. Creates a new user account if none found (with role "user", using flow from state)
  6. Returns the Kirak JWT access + refresh tokens

Response:

{
"statusCode": 200,
"status": "success",
"message": "Login successful",
"data": {
"user": { "user_id": 42, "email": "alice@gmail.com", "role": "user" },
"accessToken": "eyJhbGc...",
"refreshToken": "eyJhbGc...",
"expiresIn": 3600,
"flow": "signup",
"platform": "web"
}
}

Apple uses response_mode=form_post – the callback arrives as a form POST, not a GET with query params.

Web Platform Flow (FlutterFlow / web apps)

Section titled “Web Platform Flow (FlutterFlow / web apps)”

Apple’s form POST -> Kirak generates a short-lived web_auth_token -> redirects to auth.apple_web_callback_url?token=... -> frontend exchanges the token:

POST /auth/verify-web-auth
{ "token": "<WEB_AUTH_TOKEN>" }

Returns the flat { user, accessToken, refreshToken, expiresIn } data, same shape as every other auth route’s data (wrapped in the usual { statusCode, status, message, data } envelope). The web auth token itself is a separate, signed, short-lived (minutes) JWT – it is safe to pass in the URL.

For non-web platforms, the Apple callback returns JSON directly (same as other providers).

On first authorization, Apple sends user information (name) in the form body. Kirak reads the user field and stores first/last name. On subsequent logins, Apple does not send this – Kirak relies on the stored profile.


For mobile apps that sign the user in with a native SDK and send the token it returns:

POST /auth/{provider}/mobile

Only three providers are accepted, because only their tokens can be checked to have been issued to your app. A provider’s user API accepts an access token issued to any app, so a token from another app the user once signed in to could otherwise be replayed here as that user.

{provider} Send Checked
google-oauth2 id_token from Google Sign-In Signature (Google’s keys), issuer, expiry, and audience: one of auth.social.google.audience, default [client_id]
apple-id id_token from Sign in with Apple Signature (Apple’s keys), issuer, expiry, and audience: one of auth.social.apple.audience, default [client_id]
facebook access_token from Facebook Login Sent to Facebook with appsecret_proof, which only your app can compute; a token of another Facebook app is refused
{
"id_token": "google-or-apple-id-token",
"device_token": "optional-push-token"
}

Apple gives the user’s name only to the app, on the first sign-in; send it along then ("first_name", "last_name"). Nothing else in the body is identity: an email or a provider user id sent there is ignored.

Audiences. Google Sign-In on Android and iOS issues the ID token for the “server client id” you pass to the SDK – normally your web client id, the client_id in kirak.json. If your app does not pass one, the token is issued for the app’s own client id; list every client id that may appear: "google": {"client_id": "...", "audience": ["<web client id>", "<ios client id>"]}. For Apple, a token from the iOS app carries its bundle id instead of the web Services ID, so list both: "apple": {"audience": ["com.example.app", "com.example.app.service"]}.

{provider} must be one of auth.social_providers. The token is checked first, then the same pipeline as the web flow finds, links or creates the account, so the account is the one the provider vouches for. Any other provider (GitHub, Microsoft, LinkedIn, TikTok, …) is refused with 400 MOBILE_SIGN_IN_NOT_SUPPORTED – sign in with those through the web flow, whose code exchange needs your client secret – and so is facebook if appsecret_proof has been turned off. A missing token is a 400; a token that fails its check is 401 SOCIAL_AUTH_FAILED. As with the web flow, new users are always created with role "user", and an existing account keeps its role. The rate limit is per IP (social:mobile:ip:<ip>).


The auth_social table maps provider + uid pairs to user accounts:

auth_social
+-- user_id -> FK to users.id
+-- provider -> "google-oauth2", "github", etc.
+-- uid -> provider's user ID
+-- extra_data -> encrypted blob: provider access/refresh tokens and other profile data

extra_data is encrypted with AES-256-GCM (kirak/auth/social/encryption.py), keyed by KIRAK_AUTH_ENCRYPTION_KEY – see Provider Setup Reference below. KIRAK_AUTH_VERIFICATION_KEY (Fernet) is unrelated; it’s used for email verification and password reset links.

One user can link multiple providers. If a user signs in with a provider not yet linked and an account with the same email already exists, Kirak links the provider to that account only when the provider says the email is verified (email_verified in its data: Google and Apple send it). Otherwise the sign-in is refused with 409 SOCIAL_EMAIL_NOT_VERIFIED: linking by an unverified email would let anyone who registers that address at the provider take over the account (the risk social-core warns about for its own associate_by_email step). GitHub and Facebook do not send the flag, so their sign-ins create new accounts but do not link to existing ones. A linked account keeps its own role, so an admin who signs in with Google stays admin. A new social account is is_verified only when the provider verified its email. Exception: Instagram and TikTok don’t expose a real email address, so Kirak synthesizes a placeholder (instagram_<id>@social.kirak, tiktok_<open_id>@social.kirak) for accounts created through those providers – same-email linking does not apply to them.


@kirak.auth.hook("after_social_callback")
async def after_social_login(result):
if result.get("status") == "success":
user = result["data"]["user"]
flow = result["data"].get("flow")
if flow == "signup":
try:
await kirak.notifications.send_email({
"to": user["email"],
"template_name": "welcome",
})
except KirakException as e:
# Log error but don't block social login
print(f"Failed to send welcome email: {e.message}")
return result

The OAuth state parameter is an HMAC-signed, base64-encoded payload (kirak/auth/social/oauth_state.py) containing:

  • csrf – random token to prevent replay/forgery
  • exp – expiry timestamp; state tokens are valid for 10 minutes
  • p – client platform
  • f – auth intent (flow)
  • d – optional device token

There is no provider or role field in the state – the provider is determined by the callback URL, and role is never caller-controlled (see the warning above). The HMAC is signed with KIRAK_AUTH_JWT_SECRET_KEY. An invalid, expired, or tampered state returns 400.


Kirak auto-derives each provider’s OAuth callback URL from base_url:

{base_url}/auth/{provider}/callback

(/auth is auth_prefix when one is set.) Set "base_url": "https://api.example.com" in kirak.json (required anyway for email verification links). Register that derived URL with your provider’s dashboard. To override for a specific provider, set auth.social.<provider>.redirect_uri.

  1. Go to Google Cloud Console -> APIs & Services -> Credentials
  2. Create OAuth 2.0 Client ID (Web application)
  3. Add https://api.example.com/auth/google-oauth2/callback to Authorized redirect URIs
  4. Set the client ID in kirak.json and the secret in .env:
    "auth": { "social": { "google": { "client_id": "123456-abc.apps.googleusercontent.com" } } }
    KIRAK_AUTH_GOOGLE_CLIENT_SECRET=GOCSPX-...
  5. For mobile sign-in, pass this web client id to Google Sign-In as the server client id, or list your app’s client ids in "audience".
  1. Go to GitHub -> Settings -> Developer settings -> OAuth Apps -> New OAuth App
  2. Set Authorization callback URL: https://api.example.com/auth/github/callback
  3. Set the client ID in kirak.json (auth.social.github.client_id) and the secret in .env:
    KIRAK_AUTH_GITHUB_CLIENT_SECRET=...
  1. Apple Developer -> Certificates, Identifiers & Profiles -> Keys -> Create new key (Sign in with Apple)
  2. Download the .p8 private key file
  3. Create a Services ID for your web app; set its Return URL to https://api.example.com/auth/apple-id/callback
  4. Set the identifiers in kirak.json and the private key in .env:
    "auth": {
    "social": { "apple": { "client_id": "com.example.app.service", "team_id": "YOUR_TEAM_ID", "key_id": "ABCDE12345" } },
    "apple_web_callback_url": "https://app.example.com/auth/callback"
    }
    KIRAK_AUTH_APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..."

auth.apple_web_callback_url is the URL Kirak redirects the browser to after processing the Apple callback (e.g. your FlutterFlow web app). It is distinct from the OAuth redirect URI, which is auto-derived like all other providers.

Client ID: auth.social.facebook.client_id in kirak.json. Secret:

KIRAK_AUTH_FACEBOOK_CLIENT_SECRET=...

Register https://api.example.com/auth/facebook/callback as a Valid OAuth Redirect URI in your Facebook app settings.

Instagram does not return a real email address – accounts created through this provider get a synthesized placeholder email (see Social Account Linking above).

Client ID: auth.social.instagram.client_id in kirak.json. Secret:

KIRAK_AUTH_INSTAGRAM_CLIENT_SECRET=...

TikTok also does not return a real email address – same placeholder-email behavior as Instagram.

TikTok is not in social-core. Kirak ships a custom backend (kirak/auth/social/backends/tiktok.py) that handles TikTok’s non-standard OAuth2 flow (client_key instead of client_id, comma-separated scopes, open_id-based user identity).

{ "auth": { "social": { "tiktok": { "client_key": "...", "redirect_uri": "..." } } } }
KIRAK_AUTH_TIKTOK_CLIENT_SECRET=...
SOCIAL_AUTH_LINKEDIN_OAUTH2_KEY=your-client-id
SOCIAL_AUTH_LINKEDIN_OAUTH2_SECRET=your-client-secret

Register https://api.example.com/auth/linkedin-oauth2/callback in your LinkedIn app’s Authorized Redirect URLs.

SOCIAL_AUTH_DISCORD_KEY=your-client-id
SOCIAL_AUTH_DISCORD_SECRET=your-client-secret
SOCIAL_AUTH_MICROSOFT_GRAPH_KEY=your-client-id
SOCIAL_AUTH_MICROSOFT_GRAPH_SECRET=your-client-secret

For tenant-restricted apps, use azuread-tenant-oauth2 or azuread-v2-tenant-oauth2 instead and set SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY / SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_ID.

KIRAK_AUTH_ENCRYPTION_KEY=...

Encrypts the extra_data column in auth_social (AES-256-GCM) – the same key also encrypts MFA TOTP secrets (see MFA & OTP). If unset, Kirak derives a key from KIRAK_AUTH_JWT_SECRET_KEY and logs a startup warning – set this explicitly in production.


The FastAPIStrategy takes the host and scheme it needs to build redirect URIs from base_url in kirak.json (for example https://api.example.com). For local development without TLS use an http:// base_url. KIRAK_AUTH_HOST and KIRAK_AUTH_HTTPS are no longer read.