powered by

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
Google Yes Yes, iOS and Android
GitHub Yes No
LinkedIn 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

  1. Your app asks Anythink for an authorisation URL, passing the URL the provider should send the user back to (your redirect URI).
  2. Your app sends the browser to that URL. The user signs in with the provider and approves.
  3. The provider redirects the browser to your redirect URI with a code and a state.
  4. Your callback page passes code and state to 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:

http
GET /org/{project_id}/auth/v1/{provider}/authorize?redirectUri=https://app.example.com/auth/callback
json
{
  "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:

http
GET /org/{project_id}/auth/v1/{provider}/callback?code={code}&state={state}
json
{
  "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. localhost and Anythink's own domains are always allowed. Anything else returns 400 Invalid redirect URI.
  • Register the exact redirect URI with the provider. Providers reject a sign-in whose redirect_uri doesn't match one registered on the OAuth app, character for character.
  • The state is 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, referrer or signup_country to 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:

javascript
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.

http
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:

  1. 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.
  2. 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:

http
GET /org/{project_id}/auth/v1/providers
json
[
  { "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

  1. 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.
  2. Click Configure (or Edit) on the provider.
  3. Fill in Web sign-in, Native sign-in, or both. Each section is independent, and you can save with only one filled in.
  4. 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:

bash
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:

bash
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):

bash
anythink fetch /integrations/definitions/google/oauth
json
{
  "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)

text
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:

  1. Open your app's App ID and turn on Sign in with Apple. This is all the native flow needs.
  2. 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.
  3. Under Keys, create a key with Sign in with Apple enabled and download the .p8 file. 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

  1. On Settings › Integrations › Social Sign-In, click Configure on Apple.
  2. 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.
  3. Under Web sign-in, enter the Service ID.
  4. Leave Client secret method on .p8 self-signing (recommended) and paste the contents of the .p8 file into .p8 private key (PEM). Enter the Key ID and Team ID.
  5. Turn on Allow users without an email if you want to accept users who stop sharing their email with your app (see below).
  6. 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

bash
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)

text
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_name and family_name with 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:

http
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

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:

  1. 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.
  2. 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.
  3. For iOS, create an iOS client with your app's bundle ID and copy its client ID.
  4. 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

  1. On Settings › Integrations › Social Sign-In, click Configure on Google.
  2. Under Native sign-in, enter the iOS client ID in iOS Client IDs. Separate several with commas.
  3. Under Web sign-in, enter the Web Client ID and Client secret.
  4. 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

bash
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)

text
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:

http
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:

http
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:

json
{
  "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_in runs out, POST /org/{project_id}/auth/v1/refresh with { "token": "{refresh_token}" } for a new pair.
  • To sign out, POST /org/{project_id}/auth/v1/logout with 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:

  1. fetches the provider's public signing keys (Apple's or Google's JWKS, cached for 10 minutes and refreshed when the provider rotates keys);
  2. verifies the token's signature, issuer and expiry, with 60 seconds of clock skew;
  3. 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);
  4. checks user_identifier matches the token's subject;
  5. 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

  1. Go to Settings › Developer settings › OAuth Apps (github.com/settings/developers) and click New OAuth App.
  2. Enter an Application name and Homepage URL, and set the Authorization callback URL to your redirect URI.
  3. Register the app, copy the Client ID, and click Generate a new client secret.

Configure it in Anythink

In the Anythink dashboard

  1. On Settings › Integrations › Social Sign-In, click Configure on GitHub.
  2. Under Web sign-in, enter the Client ID and Client secret.
  3. Click Save.

With the CLI

bash
anythink integrations oauth configure github \
  --client-id Iv1.8a61f9b3a7aba766 \
  --client-secret "$GITHUB_CLIENT_SECRET"

With an AI assistant (MCP)

text
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

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

  1. In the LinkedIn Developer portal, create an app.
  2. On the Products tab, request Sign In with LinkedIn using OpenID Connect.
  3. 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

  1. On Settings › Integrations › Social Sign-In, click Configure on LinkedIn.
  2. Under Web sign-in, enter the Client ID and Client secret.
  3. For native sign-in, enter the client IDs your app's tokens are issued for in Mobile Client IDs.
  4. Click Save.

With the CLI

bash
anythink integrations oauth configure linkedin \
  --client-id 77abc123def456 \
  --client-secret "$LINKEDIN_CLIENT_SECRET"

With an AI assistant (MCP)

text
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.
Email 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.

Next steps