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.
Enabling a Provider
Section titled “Enabling a Provider”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 |
|
instagram |
|
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 |
|
| 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.
Credentials
Section titled “Credentials”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" } }}# LinkedInSOCIAL_AUTH_LINKEDIN_OAUTH2_SECRET=your-client-secret
# DiscordSOCIAL_AUTH_DISCORD_SECRET=your-client-secretThe secret variable is SOCIAL_AUTH_<BACKEND_NAME_UPPER>_SECRET: uppercase the name and replace
hyphens with underscores.
Other provider settings
Section titled “Other provider settings”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.
Redirect URI
Section titled “Redirect URI”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" } }}Custom Backends (not in social-core)
Section titled “Custom Backends (not in social-core)”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.
After upgrading social-auth-core
Section titled “After upgrading social-auth-core”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.
Checking the credentials
Section titled “Checking the credentials”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 okFor 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()).
Standard OAuth Flow (Web Browser)
Section titled “Standard OAuth Flow (Web Browser)”Step 1 – Initiate Login
Section titled “Step 1 – Initiate Login”GET /auth/{provider}/loginOptional 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:
GET /auth/google-oauth2/login?platform=web&flow=signupKirak builds an OAuth state token (HMAC-signed, short-lived) encoding these parameters, then redirects the browser to the provider’s authorization URL.
Step 2 – Provider Callback
Section titled “Step 2 – Provider Callback”GET /auth/{provider}/callback?code=...&state=...Kirak:
- Verifies the HMAC state token (and its expiry – state tokens are valid for 10 minutes)
- Exchanges the code for provider access + refresh tokens
- Fetches the user’s profile from the provider
- Looks up an existing
auth_socialrecord for this provider + uid - Creates a new user account if none found (with role
"user", usingflowfrom state) - 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 OAuth
Section titled “Apple OAuth”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.
Non-web (mobile / native)
Section titled “Non-web (mobile / native)”For non-web platforms, the Apple callback returns JSON directly (same as other providers).
First-Authorization User Data
Section titled “First-Authorization User Data”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.
Mobile Social Auth
Section titled “Mobile Social Auth”For mobile apps that sign the user in with a native SDK and send the token it returns:
POST /auth/{provider}/mobileOnly 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>).
Social Account Linking
Section titled “Social Account Linking”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 dataextra_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 resultOAuth State Security
Section titled “OAuth State Security”The OAuth state parameter is an HMAC-signed, base64-encoded payload (kirak/auth/social/oauth_state.py) containing:
csrf– random token to prevent replay/forgeryexp– expiry timestamp; state tokens are valid for 10 minutesp– client platformf– 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.
Provider Setup Reference
Section titled “Provider Setup Reference”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.
- Go to Google Cloud Console -> APIs & Services -> Credentials
- Create OAuth 2.0 Client ID (Web application)
- Add
https://api.example.com/auth/google-oauth2/callbackto Authorized redirect URIs - Set the client ID in
kirak.jsonand the secret in.env:"auth": { "social": { "google": { "client_id": "123456-abc.apps.googleusercontent.com" } } }KIRAK_AUTH_GOOGLE_CLIENT_SECRET=GOCSPX-... - 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".
GitHub
Section titled “GitHub”- Go to GitHub -> Settings -> Developer settings -> OAuth Apps -> New OAuth App
- Set Authorization callback URL:
https://api.example.com/auth/github/callback - Set the client ID in
kirak.json(auth.social.github.client_id) and the secret in.env:KIRAK_AUTH_GITHUB_CLIENT_SECRET=...
- Apple Developer -> Certificates, Identifiers & Profiles -> Keys -> Create new key (Sign in with Apple)
- Download the
.p8private key file - Create a Services ID for your web app; set its Return URL to
https://api.example.com/auth/apple-id/callback - Set the identifiers in
kirak.jsonand 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
Section titled “TikTok”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-idSOCIAL_AUTH_LINKEDIN_OAUTH2_SECRET=your-client-secretRegister https://api.example.com/auth/linkedin-oauth2/callback in your LinkedIn
app’s Authorized Redirect URLs.
Discord
Section titled “Discord”SOCIAL_AUTH_DISCORD_KEY=your-client-idSOCIAL_AUTH_DISCORD_SECRET=your-client-secretMicrosoft / Azure AD
Section titled “Microsoft / Azure AD”SOCIAL_AUTH_MICROSOFT_GRAPH_KEY=your-client-idSOCIAL_AUTH_MICROSOFT_GRAPH_SECRET=your-client-secretFor 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.
Encryption Key (all providers)
Section titled “Encryption Key (all providers)”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.
Host Configuration
Section titled “Host Configuration”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.