Skip to content

OAuth Login Flow (Google) – Overview

This document explains how a user logs into Qubital with Google OAuth, from a high-level point of view.
We focus on five main components:

  • Client – Qubital frontend (web or desktop) that the user interacts with.
  • Backend – Qubital server, owner of Qubital sessions.
  • Auth Provider – our external auth service (e.g. WorkOS) that integrates with Google.
  • Hidden OAuth Redirect Page – a hidden OAuth redirect page (registered with WorkOS) that receives the authorization code from Google and immediately redirects to the client callback URL with the code and state parameters.
  • Final Auth Page – the page shown after successful authentication is complete, with the message "You have authenticated successfully on Qubital" and the "Open Qubital" / "Cancel" options. This page is located at the redirectURI (https://api.qubital.space) that is registered in WorkOS's redirect whitelist.

Google itself is the Identity Provider (IdP) used by the auth provider.


Actors & Responsibilities

At a high level:

  • The client drives the user experience and holds the PKCE secret (codeVerifier) during the flow.
  • The backend coordinates the login, talks only to the auth provider, and creates the Qubital session cookie.
  • The auth provider (WorkOS) runs the OAuth protocol with Google, validates the authorization code and PKCE, and returns a verified identity.
  • Google authenticates the user with their Google account.
  • The Hidden OAuth Redirect Page receives the OAuth redirect from Google (with the authorization code) and immediately redirects to the client callback URL, passing along the code and state parameters.
  • The Final Auth Page is where the user lands once authentication is complete and Qubital has established its own session. This page is located at the redirectURI (https://api.qubital.space), which WorkOS keeps in its redirect whitelist.

Flowchart – High-Level OAuth Flow

%%{init: {'theme':'default', 'themeVariables': { 'fontSize':'24px'}}}%%
flowchart TB
  %% Column headers (above lanes)
  ClientHeader["Client - Qubital Frontend"]
  BackendHeader["Backend - Qubital Server"]
  AuthHeader["Auth Provider - e.g. WorkOS"]
  HiddenHeader["Hidden OAuth Redirect Page"]
  GoogleHeader["Google - Identity Provider"]

  %% Client lane (no visible lane title)
  subgraph ClientLane[" "]
    direction TB
    C1["User clicks<br/>'Sign in with Google'"]
    C2["Generate<br/>codeVerifier & challenge"]
    C3["Call Backend:<br/>start login<br/>(state, challenge)"]
    C4["Receive<br/>auth URL"]
    C5["Open Google<br/>auth URL in browser"]
    C6["Receive redirect<br/>with ?code=...<br/>(on client callback URL)"]
    C7["Call Backend:<br/>finalize login<br/>(send code + codeVerifier)"]
    C8["Load Final Auth Page<br/>('Authenticated on Qubital'<br/>'Open Qubital')"]
  end

  %% Backend lane
  subgraph BackendLane[" "]
    direction TB
    B1["Forward start-login<br/>to Auth Provider<br/>(challenge,<br/>state, redirectURI)"]
    B2["Receive auth URL<br/>from Auth Provider"]
    B3["Forward finalize-login<br/>to Auth Provider<br/>(code + codeVerifier)"]
    B4["Receive verified<br/>identity / session result"]
    B5["Create Qubital user / session<br/>Set Qubital session cookie"]
    B6["Redirect to<br/>Final Auth Page"]
  end

  %% Auth provider lane
  subgraph AuthLane[" "]
    direction TB
    A1["Store challenge<br/>& state"]
    A2["Build Google OAuth URL<br/>(client_id, scopes,<br/>PKCE, state)"]
    A3["Receive code +<br/>codeVerifier from Backend"]
    A4["Verify PKCE:<br/>recompute challenge<br/>and compare with stored"]
    A5["Exchange code with<br/>Google token endpoint<br/>(ID token + tokens)"]
    A6["Return verified<br/>identity to Backend"]
  end

  %% Hidden redirect page lane
  subgraph HiddenLane[" "]
    direction TB
    H1["Receive authorization code<br/>from Google<br/>(code & state params)"]
    H2["Redirect to Client<br/>callback URL<br/>(pass code & state)"]
  end

  %% Google lane
  subgraph GoogleLane[" "]
    direction TB
    G1["Show Google<br/>sign-in UI"]
    G2["Authenticate user<br/>& obtain consent"]
    G3["Redirect to Hidden<br/>Redirect Page<br/>with authorization code"]
    G4["Issue tokens in response<br/>to Auth Provider"]
  end

  %% Attach headers above lanes
  ClientHeader --> C1
  BackendHeader --> B1
  AuthHeader --> A1
  HiddenHeader --> H1
  GoogleHeader --> G1

  %% Flow between actors
  C1 --> C2 --> C3
  C3 --> B1 --> A1 --> A2 --> B2 --> C4 --> C5
  C5 --> G1 --> G2 --> G3 --> H1 --> H2 --> C6 --> C7
  C7 --> B3 --> A3 --> A4 --> A5 --> G4 --> A6 --> B4 --> B5 --> B6 --> C8

Step 1 – Client generates PKCE and starts the login

The flow begins when the user clicks "Sign in with Google" in the client.

Before calling our backend, the client generates a PKCE pair:

  • A random, secret codeVerifier (kept only on the client).
  • A derived code challenge (sent to the backend).

The client then sends a "start login" request to the backend, including:

  • The code challenge.
  • A state parameter for security and context tracking.

The key idea is that the codeVerifier never leaves the client at this stage. Only the one-way challenge does.

Step 2 – Backend asks the auth provider to prepare the OAuth redirect

When the backend receives the "start login" request with the code challenge, it becomes the coordinator.

The backend calls the auth provider (WorkOS), asking it to prepare an OAuth flow with Google. It forwards:

  • The PKCE code challenge.
  • The state parameter.
  • The redirectURI (https://api.qubital.space) – the Final Auth Page URL where the user will ultimately land after authentication completes.

The auth provider:

  1. Stores the code challenge, state, and redirectURI on its side.
  2. Constructs the Google OAuth authorization URL, including:
  3. Google client ID managed by the auth provider.
  4. A redirect URI pointing to WorkOS's Hidden OAuth Redirect Page.
  5. Scopes (e.g. openid, email, profile).
  6. The PKCE code challenge.
  7. Its own state parameter to protect the flow.

The auth provider then returns this authorization URL to the backend, which passes it straight back to the client.

The client now has a safe URL that leads to the Google login page. It still holds the codeVerifier, which is required later to complete the flow.

Step 3 – Client opens the Google sign-in page

The client opens the authorization URL in the user's browser. The user sees the Google sign-in page and authenticates with their Google account:

  • They may choose an account or enter credentials.
  • Google may apply MFA or other security checks.
  • If already signed in, they might only confirm access.

During this step:

  • The client is simply showing Google’s UI.
  • The auth provider and backend are waiting.
  • No Qubital session exists yet. Only Google is authenticating the user.

If the user cancels, the flow ends. If they complete the login and consent, Google issues an authorization code and redirects back.

Step 4 – Google redirects to the Hidden OAuth Redirect Page

After a successful login, Google generates a short-lived authorization code and redirects the browser to the Hidden OAuth Redirect Page (a redirect handler controlled by WorkOS).

This hidden page:

  • Is a simple redirect handler managed by WorkOS, not visible to the user.
  • Receives the authorization code and state parameters from Google.
  • Immediately redirects the browser to the client callback URL with these parameters.

The redirect URL to the client contains:

  • code=... – the authorization code from Google.
  • state=... – the auth provider's state value.

This intermediate step is necessary because Google's OAuth flow requires a redirect URI controlled by the auth provider (WorkOS), which then forwards the authorization code to the client application.


Step 5 – Client receives the authorization code

The client now receives the redirect with the authorization code at its callback URL. At this moment:

  • The client still has the original codeVerifier.
  • It now has the authorization code.
  • It still does not have a Qubital session.

The next step is to send both the authorization code and the codeVerifier to the backend so that Qubital can finalize the login.

Step 6 – Client sends the code and verifier to the backend

From the callback page, the client calls the backend again, this time to complete authentication.

The request includes:

  • The authorization code received from Google.
  • The codeVerifier that matches the original code challenge.

This is the critical proof that:

  • The same client that started the flow (with the original code challenge) is now trying to finish it, and
  • The authorization code belongs to this transaction.

The backend now forwards this information to the auth provider to perform the final validations and token exchange with Google.

Step 7 – Auth provider validates PKCE and exchanges the code

The auth provider receives from the backend:

  • The authorization code.
  • The codeVerifier.

It performs two key checks:

  1. PKCE verification
    It recomputes a code challenge from the provided codeVerifier and compares it to the originally stored challenge for this transaction.
  2. If they do not match, the request is rejected.
  3. If they match, it proves the authorization code is being redeemed by the correct client.

  4. Code exchange with Google
    The auth provider sends the authorization code (plus its own credentials and redirect URI) to Google’s token endpoint.
    Google verifies the code, and if everything is valid, returns tokens and identity information (e.g. ID token, user profile).

If both steps succeed, the auth provider builds an authentication result and returns it to our backend. Conceptually, this result says:

“Google has authenticated this user; here is the verified identity and tokens.”

The auth provider may also maintain its own session internally, but the Qubital user session is created only by our backend.

Step 8 – Backend creates the Qubital session and shows the final auth page

With the verified identity in hand, the backend now:

  1. Looks up or creates the corresponding Qubital user in our database.
  2. Creates a Qubital session bound to that user.
  3. Sets a Qubital session cookie in the HTTP response:
  4. Scoped to our domain.
  5. HttpOnly and Secure.

The backend then redirects the browser to the Final Auth Page (located at the redirectURI: https://api.qubital.space).

This page:

  • Confirms to the user that they have authenticated successfully on Qubital.
  • Displays actions such as “Open Qubital” and “Cancel”.

By the time this page loads:

  • The Qubital session cookie is already set.
  • From our backend’s perspective, the user is fully authenticated.

When the user clicks “Open Qubital”, the client can either:

  • Navigate to the main web app area using the existing session, or
  • Trigger a deep link to the desktop app, if that is the desired flow.

No additional OAuth work happens after this page. It is purely the handoff from authentication to application usage.