Skip to content

Social sign-in

Your app’s users can sign in with Google or GitHub. Each project uses its own OAuth app from each provider, so the consent screen shows your app’s name.

This is sign-in for your app’s users. Signing in to the VAIF Studio console is separate; see Single sign-on.

  1. Create an OAuth app at the provider.

    • Google: Google Cloud console > APIs & Services > Credentials > Create credentials > OAuth client ID, type Web application.
    • GitHub: Settings > Developer settings > OAuth Apps > New OAuth App.
  2. Register the callback URL. In the console, open Authentication > Providers and choose the provider. Copy the Callback URL it shows into the provider: Google calls it an Authorized redirect URI, GitHub an Authorization callback URL. It looks like this:

    https://api.vaif.studio/projects/{projectId}/auth/oauth/google/callback

    The provider sends its code to VAIF, where your client secret is. It never goes to your app.

  3. Paste the client ID and secret and turn the provider on. The secret is stored encrypted and never shown again. To change other settings later, leave the secret blank and the saved one is kept.

  4. Add your app’s origin to the allowed redirect URLs (Authentication > URL configuration), for example https://myapp.com. After sign-in, VAIF only sends users back to an origin on that list. It must use https, except http://localhost for development. With an empty list, sign-in is refused.

Social sign-in needs a plan that includes it, and how many providers a project can turn on depends on the plan. See Plans.

With @vaifstudio/client, create the client with your project ID and publishable key, as for any other sign-in:

import { createVaifClient } from "@vaifstudio/client";
const vaif = createVaifClient({
baseUrl: "https://api.vaif.studio",
projectId: "YOUR_PROJECT_ID",
apiKey: "YOUR_PUBLISHABLE_KEY",
});
// On your sign-in page: sends the browser to the provider's consent screen.
await vaif.auth.signInWithOAuth({ provider: "google", redirectUrl: "https://myapp.com/auth/callback" });

On the page you named as redirectUrl, finish the sign-in:

// https://myapp.com/auth/callback
const result = await vaif.auth.handleOAuthCallback();
if ("mfaRequired" in result) {
// The user has two-factor sign-in on: ask for their code, then
// await vaif.auth.mfa.verify(result.challengeToken, code);
} else {
console.log("signed in", result.user.id, result.isNewUser ? "(new account)" : "");
// result.accessToken is the same kind of token /auth/login returns.
}

handleOAuthCallback() reads ?code and ?state from the address bar, exchanges them for a session, and removes them from the URL. The session is the same as an email and password sign-in: see Session management.

To render buttons only for the providers you turned on, call vaif.auth.listOAuthProviders().

  • PKCE and a one-time code. The SDK creates a PKCE pair and keeps the verifier in sessionStorage until the callback. The redirect back to your app carries only a one-time code that expires after 5 minutes and is useless without that verifier. No token is ever put in a URL.
  • State is checked. A callback this browser did not start is refused (OAUTH_STATE_MISMATCH), so nobody can sign a visitor in to someone else’s account.
  • Exact redirects. Only origins on the allowed redirect URLs, checked when the sign-in starts and again when it returns.

On a server or in a native app, call getOAuthSignInUrl(), open the url it returns, and keep its codeVerifier. When the user lands on your redirect URL, pass the values back:

const { url, state, codeVerifier } = await vaif.auth.getOAuthSignInUrl({
provider: "github",
redirectUrl: "https://myapp.com/auth/callback",
});
// ...open url, then on the callback:
const session = await vaif.auth.handleOAuthCallback({ code: "CODE_FROM_THE_URL", state, codeVerifier });
  • A provider account that has signed in before always signs in to the same user.
  • The provider must vouch for the email address. A GitHub account with no verified email is refused (email_not_verified).
  • Linking by verified email only. When a new provider account’s verified email matches an existing user whose email is also verified, it signs in to that user. If the existing user’s email was never verified, the sign-in is refused (account_exists): the user signs in with their password, verifies their email, and can then use social sign-in.
  • Otherwise a new user is created (if Allow sign-up is on) with the email marked verified and no password. The user can add one later with a password reset.
  • Suspended and locked users cannot sign in this way either, and a user with two-factor sign-in on is always asked for their code.

When a sign-in is refused, the browser comes back to your redirect URL with ?error= and a readable error_description. handleOAuthCallback() throws a VaifError whose code is OAUTH_ plus the error in capitals, with the description as its message:

error Meaning
access_denied The user cancelled on the provider’s screen
email_not_verified The provider account has no verified email
account_exists A user with this email exists but their email is not verified
identity_conflict The user already has a different account from this provider
signup_disabled New sign-ups are turned off for the project
quota_exceeded The project cannot take new users on its plan
user_banned / account_locked The user is suspended or temporarily locked
provider_not_enabled / provider_error The provider is off, or the provider could not complete the sign-in
Route Use
GET /projects/{projectId}/auth/oauth/providers Providers turned on for the project
POST /projects/{projectId}/auth/oauth/{provider}/start Body { redirectUrl, codeChallenge, codeChallengeMethod: "S256", state }; answers { url }, the consent page
GET /projects/{projectId}/auth/oauth/{provider}/callback Where the provider returns; redirects to your app
POST /projects/{projectId}/auth/oauth/token Body { code, codeVerifier }; answers the same shape as /auth/login
GET /projects/{projectId}/auth/oauth-apps Provider setup and each provider’s callback URL, for project developers
PUT /projects/{projectId}/auth/oauth-apps/{provider} Body { clientId, clientSecret, enabled } (the secret only when setting it); project admins
DELETE /projects/{projectId}/auth/oauth-apps/{provider} Remove a provider; project admins