Social sign-in: Apple, Google, GitHub
Add Apple, Google, GitHub and LinkedIn sign-in to web and mobile apps, with native iOS and Android token exchange and automatic account linking.
Last updated
Social sign-in lets your users sign in with an account they already have: Apple, Google, GitHub or LinkedIn. Anythink handles the OAuth exchange, verifies the identity, finds or creates the user in your project, and returns the same access and refresh tokens as email and password sign-in. Your app then calls the REST API with those tokens as normal.
Each provider is configured once per project, in the Anythink dashboard or through the API. Client secrets and Apple private keys are encrypted when you save them and are never shown again.
| Provider | Web (browser redirect) | Native (mobile token exchange) |
|---|---|---|
| Apple | Yes | Yes, iOS |
| Yes | Yes, iOS and Android | |
| GitHub | Yes | No |
| Yes | Yes, with a LinkedIn OpenID Connect ID token |
How social sign-in works
There are two flows. Use the web flow for browser apps and the native flow for mobile apps that use the provider's own SDK. A provider can have both configured at once.
Web flow: browser redirect
- Your app asks Anythink for an authorisation URL, passing the URL the provider should send the user back to (your redirect URI).
- Your app sends the browser to that URL. The user signs in with the provider and approves.
- The provider redirects the browser to your redirect URI with a
codeand astate. - Your callback page passes
codeandstateto Anythink. Anythink exchanges the code for the provider's tokens server-side, reads the user's profile, signs the user in and returns Anythink tokens.
The client secret never leaves Anythink, and your app never sees the provider's tokens.
Start the flow:
GET /org/{project_id}/auth/v1/{provider}/authorize?redirectUri=https://app.example.com/auth/callback
{
"authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=…&redirect_uri=…&response_type=code&scope=openid%20email%20profile&state=…"
}
Then, on your callback page, finish it:
GET /org/{project_id}/auth/v1/{provider}/callback?code={code}&state={state}
{
"access_token": "eyJhbGciOi…",
"refresh_token": "kq2V…",
"expires_in": 1800
}
Rules for the web flow:
- The redirect URI must be on an allowed origin. Add your app's origin under Settings › Organisation Settings › Allowed Application URLs.
localhostand Anythink's own domains are always allowed. Anything else returns400 Invalid redirect URI. - Register the exact redirect URI with the provider. Providers reject a sign-in whose
redirect_uridoesn't match one registered on the OAuth app, character for character. - The
stateis single use and lasts 10 minutes. Start a fresh sign-in if the user takes longer. - Signup attribution is optional. Add
utm_source,utm_medium,utm_campaign,utm_term,utm_content,referrerorsignup_countryto the authorise request and Anythink records them against a newly created user.
With the Anythink SDK, store the tokens with setSession and the SDK keeps the session refreshed from then on:
const res = await fetch(
`${API}/org/${PROJECT_ID}/auth/v1/google/callback?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`
);
if (!res.ok) throw new Error(await res.text());
const tokens = await res.json();
await getAuthClient().setSession(tokens);
Native flow: token exchange
On iOS and Android the provider's SDK signs the user in on the device and hands your app a signed identity token (a JWT). Your app posts that token to Anythink. Anythink checks its signature against the provider's published keys, checks it was issued for one of your app's client IDs, and signs the user in. No redirect, no web view and no client secret.
POST /org/{project_id}/auth/v1/native/{provider}
Content-Type: application/json
| Field | Required | Description |
|---|---|---|
identity_token |
Yes | The ID token from the provider's SDK. |
user_identifier |
Yes | The provider's stable user ID. It must equal the token's sub claim, or the request is rejected. |
given_name |
No | First name, used when the user is created. Apple only sends the name on the first sign-in, so pass it when you have it. |
family_name |
No | Last name, as above. |
signup_context |
No | Attribution for new users: utm_source, utm_medium, utm_campaign, utm_term, utm_content, referrer, signup_country. |
The response is the same token pair as the web flow. The email address always comes from the verified token, never from the request body.
| Status | Body | Cause |
|---|---|---|
200 |
access_token, refresh_token, expires_in |
Signed in. |
400 |
{Provider} is not configured for this organisation |
Native sign-in isn't set up for the provider in this project. |
400 |
Native sign-in not supported for '{provider}' |
The provider has no native flow (for example, GitHub). |
401 |
Token audience not allowed… |
The token was issued for a client ID that isn't in the provider's native list. |
401 |
Token expired |
Identity tokens are short-lived. Get a fresh one from the SDK and post it straight away. |
401 |
Subject mismatch between token and request |
user_identifier isn't the token's sub. |
401 |
No email returned from the provider… |
First sign-in without an email address. See Apple. |
401 |
User account is not active |
The user has been deactivated in this project. |
Native Apple and Google sign-in below has a full mobile example.
Account linking: which user signs in
Anythink matches a social sign-in to a user in this order:
- By provider identity. A native sign-in records the provider's subject ID on the user the first time. Every later native sign-in with that subject goes straight to the same user, even if the email changes, which matters for Apple's private relay addresses.
- By email. Otherwise Anythink looks up the email address the provider returned:
- a user in this project with that email signs in to their existing account, whichever way they signed up;
- a user who only exists in another Anythink project is added to this one;
- otherwise a new user is created with the project's default role.
New and matched users are marked as confirmed, because the provider has verified the address. The email must be verified by the provider unless your project doesn't require email confirmation, and your project must have a default role for new users, or sign-in fails with 400.
Each user shows the provider they last signed in with in the Auth column of Settings › Users and on their user page, and the API returns it as auth_provider on the user. A user who signs in with Google and later with Apple, using the same email, is one user. Manage users covers the rest of the user lifecycle.
List the enabled providers
Your sign-in page can ask Anythink which buttons to show. This endpoint is public, so call it before the user is signed in:
GET /org/{project_id}/auth/v1/providers
[
{ "provider": "google", "display_name": "Google", "icon": "google" },
{ "provider": "github", "display_name": "GitHub", "icon": "github" }
]
A provider is listed once its web flow is configured and enabled. Native-only providers aren't listed, because they don't need a button that redirects.
Configure a provider
Every provider follows the same pattern: create an OAuth app in the provider's developer console, then give Anythink its credentials.
In the Anythink dashboard
- Go to Settings › Social Sign-In. This opens Settings › Integrations on the Social Sign-In tab, with a card per provider showing Web and Native status.
- Click Configure (or Edit) on the provider.
- Fill in Web sign-in, Native sign-in, or both. Each section is independent, and you can save with only one filled in.
- Click Save. The card's badges change to Web On and Native On for the flows you configured.
To replace a stored client secret or key, paste the new value. Leave a stored secret blank to keep it.
With the CLI
For a web-only provider, set the client ID and secret:
anythink integrations oauth configure github \
--client-id Iv1.8a61f9b3a7aba766 \
--client-secret "$GITHUB_CLIENT_SECRET"
This sets the web flow only. For native client IDs or Apple's signing key, send the full settings to the API with anythink fetch, as shown in each provider's section. The request replaces the provider's web client ID and native client IDs together, so always send both:
anythink fetch /integrations/definitions/google/oauth --method PUT --body '{
"web_client_id": "1234567890-web.apps.googleusercontent.com",
"web_client_secret": "'"$GOOGLE_CLIENT_SECRET"'",
"native_audiences": ["1234567890-ios.apps.googleusercontent.com"],
"is_enabled": true,
"use_social_sign_in": false
}'
Check what's configured (secrets are reported as present or not, never returned):
anythink fetch /integrations/definitions/google/oauth
{
"native_audiences": ["1234567890-ios.apps.googleusercontent.com"],
"web_client_id": "1234567890-web.apps.googleusercontent.com",
"has_web_client_secret": true,
"native_flow_configured": true,
"web_flow_configured": true,
"is_enabled": true
}
Changing these settings needs the anythink_integrations:update permission.
With an AI assistant (MCP)
Set up GitHub sign-in for this project. The client ID is Iv1.8a61f9b3a7aba766 and the secret is in my GITHUB_CLIENT_SECRET environment variable. Then show me which social sign-in providers are enabled.
The assistant runs the CLI through the Anythink MCP server's cli tool: integrations oauth configure github … to save the credentials, then fetch /auth/v1/providers to list the enabled providers. Anything you paste into the chat, including a secret, passes through the assistant, so prefer the dashboard or the CLI for client secrets and Apple keys. Connect an AI assistant sets up the MCP server.
Apple
Sign in with Apple works on the web and natively on iOS. The web flow needs a Services ID and a signing key; the native flow only needs your app's bundle ID.
Set it up in Apple Developer
In Certificates, Identifiers & Profiles:
- Open your app's App ID and turn on Sign in with Apple. This is all the native flow needs.
- For the web flow, create a Services ID (for example
com.example.app.signin) and turn on Sign in with Apple for it. Under Configure, add your website's domain and your redirect URI as a Return URL. - Under Keys, create a key with Sign in with Apple enabled and download the
.p8file. Apple only lets you download it once. Note its Key ID, and your Team ID from the top right of the developer portal.
Configure it in Anythink
In the Anythink dashboard
- On Settings › Integrations › Social Sign-In, click Configure on Apple.
- Under Native sign-in, enter your app's bundle identifier in Bundle IDs. Separate several with commas, for example a production and a development build.
- Under Web sign-in, enter the Service ID.
- Leave Client secret method on .p8 self-signing (recommended) and paste the contents of the
.p8file into .p8 private key (PEM). Enter the Key ID and Team ID. - Turn on Allow users without an email if you want to accept users who stop sharing their email with your app (see below).
- Click Save.
With .p8 self-signing, Anythink signs a fresh client secret for every token exchange, so there's nothing to rotate. The alternative, Static client_secret JWT, takes a client secret you have signed yourself. Apple caps those at six months, so you'd need to replace it before it expires.
With the CLI
anythink fetch /integrations/definitions/apple/oauth --method PUT --body "$(jq -n \
--rawfile key AuthKey_ABC123DEF4.p8 \
'{
native_audiences: ["com.example.app"],
web_client_id: "com.example.app.signin",
apple_private_key: $key,
apple_key_id: "ABC123DEF4",
apple_team_id: "ABCD123456",
allow_missing_email: false,
is_enabled: true,
use_social_sign_in: false
}')"
jq --rawfile keeps the key's line breaks intact inside the JSON. For native sign-in only, send native_audiences and leave out the web fields.
With an AI assistant (MCP)
Turn on native Sign in with Apple for this project for the bundle ID com.example.app.
The assistant uses the cli tool to fetch the Apple settings endpoint with native_audiences. Configure the .p8 key in the dashboard or the CLI rather than pasting it into a chat.
Apple's privacy features
- Hide My Email. Users can share a private relay address instead of their real one. Anythink treats it as a normal verified email, and native sign-in keeps the user linked by Apple's subject ID if the address changes.
- Name on first sign-in only. Apple returns the user's name once, on the first authorisation. Send
given_nameandfamily_namewith that native request; later sign-ins don't need them. - No email at all. If a user revokes email sharing, Apple's token has no email. With Allow users without an email on, Anythink creates the user with a stable placeholder address, unique to that Apple account, so they can still sign in natively. With it off, a first sign-in without an email is refused with
401.
Apple's web flow
Apple returns the sign-in result as a form POST to your redirect URI rather than a browser redirect. Your redirect URI must therefore be the Anythink callback, served from your app's origin and forwarded to the Anythink API: https://app.example.com/org/{project_id}/auth/v1/apple/callback. Anythink completes the sign-in, then redirects the browser to https://app.example.com/org/{project_id}/auth/apple/callback?code={one_time_code}. That page exchanges the one-time code for tokens:
POST /org/{project_id}/auth/v1/exchange
Content-Type: application/json
{ "code": "{one_time_code}" }
The response is the usual token pair. The one-time code works once and expires after two minutes, which keeps tokens out of URLs and browser history.
Google sign-in works on the web and natively on iOS and Android. Anythink requests the openid, email and profile scopes.
Set it up in Google Cloud
In the Google Cloud console:
- Create or select a project and configure the OAuth consent screen. While its publishing status is Testing, only the test users you add can sign in.
- Under Credentials › Create credentials › OAuth client ID, create a Web application client. Add your redirect URI under Authorized redirect URIs. Copy the Client ID and Client secret.
- For iOS, create an iOS client with your app's bundle ID and copy its client ID.
- For Android, create an Android client with your package name and SHA-1 signing certificate fingerprint. Your app doesn't present this client's ID: on Android the ID token is issued for your web client ID, which your app passes to Google's SDK as the server client ID.
Configure it in Anythink
In the Anythink dashboard
- On Settings › Integrations › Social Sign-In, click Configure on Google.
- Under Native sign-in, enter the iOS client ID in iOS Client IDs. Separate several with commas.
- Under Web sign-in, enter the Web Client ID and Client secret.
- Click Save.
Anythink accepts native tokens issued for any client ID in the native list or for the web client ID. So Android sign-in works as soon as the Web Client ID is saved. For an Android-only app with no web sign-in, put the web client ID in the native list instead.
With the CLI
anythink fetch /integrations/definitions/google/oauth --method PUT --body '{
"web_client_id": "1234567890-web.apps.googleusercontent.com",
"web_client_secret": "'"$GOOGLE_CLIENT_SECRET"'",
"native_audiences": ["1234567890-ios.apps.googleusercontent.com"],
"is_enabled": true,
"use_social_sign_in": false
}'
For web sign-in only, anythink integrations oauth configure google --client-id … --client-secret … does the same without the native list.
With an AI assistant (MCP)
Enable native Google sign-in for my iOS app with client ID 1234567890-ios.apps.googleusercontent.com, keeping the existing web client ID.
The assistant reads the current settings with fetch /integrations/definitions/google/oauth, then saves them back with the iOS client ID added to native_audiences.
If you also connect Google Calendar for workflows, it can reuse this OAuth client. See Connect Google Calendar.
Native Apple and Google sign-in for iOS and Android
This is the whole native integration. The provider's SDK shows its own sign-in sheet, your app gets an identity token, and one request to Anythink turns it into an Anythink session.
What the app sends
Apple on iOS. After ASAuthorizationAppleIDProvider (or a cross-platform plugin) returns an ASAuthorizationAppleIDCredential:
| Request field | Value from Apple |
|---|---|
identity_token |
credential.identityToken, decoded from UTF-8 data to a string |
user_identifier |
credential.user |
given_name, family_name |
credential.fullName, present on the first sign-in only |
Google on iOS and Android. After Google Sign-In (iOS) or Credential Manager's Google ID option (Android, with your web client ID as the server client ID):
| Request field | Value from Google |
|---|---|
identity_token |
the ID token string (user.idToken.tokenString on iOS, GoogleIdTokenCredential.idToken on Android) |
user_identifier |
the token's sub claim: user.userID on iOS; on Android, decode the token's payload and read sub |
given_name, family_name |
the profile's given and family name |
A worked example
A Swift app signing in with Apple:
func signInWithAnythink(_ credential: ASAuthorizationAppleIDCredential) async throws -> AnythinkSession {
guard let tokenData = credential.identityToken,
let identityToken = String(data: tokenData, encoding: .utf8) else {
throw SignInError.missingToken
}
var request = URLRequest(url: URL(string:
"https://api.my.anythink.cloud/org/\(projectId)/auth/v1/native/apple")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"identity_token": identityToken,
"user_identifier": credential.user,
"given_name": credential.fullName?.givenName as Any,
"family_name": credential.fullName?.familyName as Any,
].compactMapValues { $0 is NSNull ? nil : $0 })
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
throw SignInError.rejected(String(decoding: data, as: UTF8.self))
}
return try JSONDecoder.snakeCase.decode(AnythinkSession.self, from: data)
}
The request on the wire:
POST /org/48886594/auth/v1/native/apple
Content-Type: application/json
{
"identity_token": "eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmV4YW1wbGUuYXBwIiwic3ViIjoiMDAxMjM0LmFiY2QuLi4ifQ.…",
"user_identifier": "001234.5f9c1a2b3c4d4e5f8a9b0c1d2e3f4a5b.1203",
"given_name": "Ada",
"family_name": "Lovelace"
}
What comes back:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"refresh_token": "Yt7Qm1x…",
"expires_in": 1800
}
From here the app is signed in like any other user:
- Send
Authorization: Bearer {access_token}on every API call. - Before
expires_inruns out,POST /org/{project_id}/auth/v1/refreshwith{ "token": "{refresh_token}" }for a new pair. - To sign out,
POST /org/{project_id}/auth/v1/logoutwith the same body, which revokes the refresh token.
Store both tokens in the Keychain on iOS or encrypted storage on Android, not in plain preferences. The Google request is identical apart from the path, /auth/v1/native/google, and where the fields come from.
What Anythink checks
For every native request, Anythink:
- fetches the provider's public signing keys (Apple's or Google's JWKS, cached for 10 minutes and refreshed when the provider rotates keys);
- verifies the token's signature, issuer and expiry, with 60 seconds of clock skew;
- checks the token's audience is one of the client IDs configured for the provider (bundle IDs for Apple; iOS client IDs or the web client ID for Google);
- checks
user_identifiermatches the token's subject; - runs account linking and issues a session.
There's no client secret in the native flow, so nothing secret ships inside your app.
GitHub
GitHub sign-in works on the web. Anythink requests the user:email and read:user scopes. If a user keeps their email private on their profile, Anythink reads their primary email address instead.
Set it up in GitHub
- Go to Settings › Developer settings › OAuth Apps (github.com/settings/developers) and click New OAuth App.
- Enter an Application name and Homepage URL, and set the Authorization callback URL to your redirect URI.
- Register the app, copy the Client ID, and click Generate a new client secret.
Configure it in Anythink
In the Anythink dashboard
- On Settings › Integrations › Social Sign-In, click Configure on GitHub.
- Under Web sign-in, enter the Client ID and Client secret.
- Click Save.
With the CLI
anythink integrations oauth configure github \
--client-id Iv1.8a61f9b3a7aba766 \
--client-secret "$GITHUB_CLIENT_SECRET"
With an AI assistant (MCP)
Configure GitHub sign-in with client ID Iv1.8a61f9b3a7aba766, using the secret in GITHUB_CLIENT_SECRET.
The assistant runs integrations oauth configure github through the cli tool.
One OAuth app per purpose
A GitHub OAuth app has a single callback URL. If you also use GitHub in workflows (for example, to read repositories), create a separate OAuth app for that integration and configure it under the Integrations tab. See How integrations work.
LinkedIn sign-in works on the web, using LinkedIn's OpenID Connect product. Anythink requests the openid, profile and email scopes. Anythink also accepts LinkedIn OpenID Connect ID tokens on the native endpoint, /auth/v1/native/linkedin, for apps that sign users in to LinkedIn themselves.
Set it up in LinkedIn
- In the LinkedIn Developer portal, create an app.
- On the Products tab, request Sign In with LinkedIn using OpenID Connect.
- On the Auth tab, add your redirect URI under Authorized redirect URLs for your app, and copy the Client ID and Primary Client Secret.
Configure it in Anythink
In the Anythink dashboard
- On Settings › Integrations › Social Sign-In, click Configure on LinkedIn.
- Under Web sign-in, enter the Client ID and Client secret.
- For native sign-in, enter the client IDs your app's tokens are issued for in Mobile Client IDs.
- Click Save.
With the CLI
anythink integrations oauth configure linkedin \
--client-id 77abc123def456 \
--client-secret "$LINKEDIN_CLIENT_SECRET"
With an AI assistant (MCP)
Set up LinkedIn sign-in with client ID 77abc123def456 and the secret in LINKEDIN_CLIENT_SECRET.
The assistant runs integrations oauth configure linkedin through the cli tool.
Slack and X
Slack and X (Twitter) are available as workflow integrations, for posting messages and tweets from a workflow step, but not as sign-in providers. See Connect Slack and How integrations work.
Limits and trade-offs
| Limit | Detail |
|---|---|
| Providers | Apple, Google, GitHub and LinkedIn. |
| Native flow | Apple, Google and LinkedIn. GitHub is web only. |
| Sign-in state | Single use, 10 minutes from the authorise request. |
| Apple one-time code | Single use, 2 minutes. |
| Required on first sign-in, and must be verified unless your project doesn't require email confirmation. Apple users without an email need Allow users without an email. | |
| Linking by subject ID | Native sign-ins only. Web sign-ins match users by email every time. |
| Provider tokens | Used once to read the profile, then discarded. To call a provider's API on the user's behalf, use a user connection. |