BASE44DEVS

FIX · AUTH · MEDIUM

Base44 Third-Party OAuth Broken — Fix GitHub, Microsoft & Slack

Base44 third-party OAuth breaks because one of five flow steps is wrong: the authorization URL is missing scopes or the tenant ID, the redirect URI is not registered exactly as Base44 sends it, the callback handler does not validate the state parameter, the token-exchange POST omits the client secret or uses the wrong content type, or the profile fetch hits the wrong endpoint for that provider. The Google-specific failure has its own dedicated fix page. For GitHub, Microsoft Entra ID, Slack, generic OIDC, and custom providers, the diagnostic is identical: capture the callback URL parameters in your browser, inspect the token-exchange request and response in your function logs, confirm the registered redirect URI byte-for-byte matches Base44's outgoing redirect, and verify your requested scopes cover every field your profile fetch reads.

Last verified
2026-05-24
Category
AUTH
Difficulty
MODERATE
DIY possible
YES

Why your base44 third-party OAuth is broken

Base44 third-party OAuth fails because one of five flow steps is wrong: the authorization URL is missing scopes or the tenant ID, the redirect URI is not registered exactly as Base44 sends it, the callback handler does not validate the state parameter, the token-exchange POST omits the client secret or uses the wrong content type, or the profile fetch hits the wrong endpoint for that provider. The Google-specific failure has its own dedicated fix page. For GitHub, Microsoft Entra ID, Slack, generic OIDC, and custom providers, the diagnostic is identical: capture the callback URL parameters in your browser, inspect the token-exchange request and response in your function logs, confirm the registered redirect URI byte-for-byte matches Base44's outgoing redirect, and verify your requested scopes cover every field your profile fetch reads.

You wired up GitHub login. You tested it. It worked in the editor preview. You shipped to production and the first real user gets bounced back to your homepage with no session. Or worse, the provider's consent screen never appears and the user lands on an error page from accounts.github.com or login.microsoftonline.com with a cryptic invalid_request message.

Base44's authentication layer handles email-and-password and Google sign-in cleanly enough for most teams, but third-party OAuth (GitHub, Microsoft Entra ID, Slack, generic OIDC, and any custom-built provider) involves five distinct steps and any one of them can break in a way that produces a silent failure or a misleading error. The platform-provided wiring is thin; you usually own the callback handler, the token exchange, and the profile-to-session mapping inside a Base44 function. Most of the bugs live in those three places.

This guide covers the non-Google providers. The Google-specific fix is on its own page because Google is the single most common case and has its own consent-screen approval flow that no other provider shares.

What causes base44 third-party OAuth integration to fail

There are five steps in the OAuth 2.0 Authorization Code flow as Base44 implements it, and each one has its own failure modes.

Step 1 — Build the authorization URL. Your app constructs a URL pointing at the provider's /authorize endpoint with query parameters: client_id, redirect_uri, response_type=code, scope, state, and (for PKCE) code_challenge + code_challenge_method. Mistakes here produce a provider-side error page before the user ever sees a consent screen. The most common bugs are a wrong tenant in the URL path (Microsoft), a missing or unregistered scope, a redirect_uri the provider does not recognize, and an empty state parameter.

Step 2 — User authenticates and consents. The provider shows its login page and a consent screen listing the scopes you requested. The user accepts or declines. There is almost nothing your code can do here — failures at this step are either configuration (your app is in test mode and the user is not a tester) or user choice (they hit cancel).

Step 3 — Provider redirects to your callback URL with a code. The provider 302s back to the redirect_uri you registered, appending code and state (and sometimes scope) as query parameters. If anything was wrong upstream, you get error and error_description instead. Two things go wrong at this step: the registered redirect URI does not exactly match the URL Base44 served (different host, missing trailing slash, http vs https), or your callback handler does not read the query parameters correctly because it expects them on a different HTTP method or path.

Step 4 — Token exchange. Your callback handler POSTs to the provider's /token endpoint with grant_type=authorization_code, code, redirect_uri, client_id, and either client_secret or code_verifier (PKCE). The provider returns an access_token, a token_type, an expires_in, and optionally an id_token (OIDC) and a refresh_token. This step fails in three ways: wrong content type (sending JSON when the provider requires form-encoded), wrong authentication (secret in body vs HTTP Basic), and a mismatched redirect_uri (it must be identical to step 1).

Step 5 — Profile fetch and session create. Your code uses the access token to fetch the user's profile from the provider's userinfo endpoint and creates a session in your app. This step fails when the provider's response shape does not match your assumptions — email vs mail, id vs sub vs oid, missing fields because you did not request the right scope, or a different endpoint for the same product (GitHub's /user/emails vs /user).

A working OAuth integration handles all five steps correctly. A broken one is broken at exactly one of them — find which one and the fix is usually a one-line change.

Source: RFC 6749 (OAuth 2.0), RFC 7636 (PKCE), OpenID Connect Core 1.0, and the provider-specific docs cited per section below.

Provider-specific gotchas

GitHub OAuth Apps

GitHub has two products that both let users sign in with their GitHub account: OAuth Apps and GitHub Apps. They are not interchangeable. OAuth Apps use the classic Authorization Code flow with user-to-server tokens. GitHub Apps use installation tokens scoped to specific repositories and a slightly different flow. For "Sign in with GitHub" in a SaaS app, you want OAuth Apps.

GitHub requires explicit scopes for email and profile data. Without read:user, your call to https://api.github.com/user returns the public profile only and the email field is null for any user who has not made their primary email public. The fix is to request read:user user:email and then separately call https://api.github.com/user/emails to get the verified primary email:

// Inside a Base44 function — GitHub OAuth callback handler
export async function handler(req) {
  const { code, state } = req.query;
  if (!validateState(state)) return error(403, "state mismatch");

  // Step 4 — token exchange. GitHub accepts JSON body if you set Accept.
  const tokenRes = await fetch("https://github.com/login/oauth/access_token", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Accept": "application/json",
    },
    body: JSON.stringify({
      client_id: process.env.GITHUB_CLIENT_ID,
      client_secret: process.env.GITHUB_CLIENT_SECRET,
      code,
      redirect_uri: process.env.GITHUB_REDIRECT_URI,
    }),
  });
  const { access_token, error: tokenError } = await tokenRes.json();
  if (tokenError) return error(400, tokenError);

  // Step 5 — profile fetch. Need TWO calls for email.
  const auth = { Authorization: `Bearer ${access_token}` };
  const [profile, emails] = await Promise.all([
    fetch("https://api.github.com/user", { headers: auth }).then((r) => r.json()),
    fetch("https://api.github.com/user/emails", { headers: auth }).then((r) => r.json()),
  ]);
  const primaryEmail = emails.find((e) => e.primary && e.verified)?.email;
  if (!primaryEmail) return error(400, "no verified primary email");

  return createSession({ provider: "github", externalId: profile.id, email: primaryEmail, name: profile.name });
}

The other GitHub-specific trap is the homepage URL. GitHub requires Homepage URL and Authorization callback URL to be set on the OAuth App, and the callback URL is matched exactly. https://app.example.com/auth/github/callback does not match https://app.example.com/auth/github/callback/ — note the trailing slash.

Microsoft Entra ID (formerly Azure AD)

Microsoft is the OAuth provider with the most ways to get the URL wrong. The authorization endpoint is https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize where {tenant} is one of:

  • A specific tenant directory ID (a GUID) — restricts sign-in to that organization
  • common — accepts any work, school, or personal Microsoft account
  • organizations — accepts work and school accounts but not personal
  • consumers — accepts personal Microsoft accounts only

Pick the wrong one and either the wrong users can sign in or the right users cannot. For a B2B SaaS where any organization should be able to sign in, use organizations. For consumer-facing apps, use consumers or common.

The v1.0 vs v2.0 split is the other Microsoft trap. The v1.0 endpoints (/oauth2/authorize, /oauth2/token) are the old Azure AD flow that returned a different ID token format and used different scope syntax (resource URIs instead of named scopes). The v2.0 endpoints (/oauth2/v2.0/authorize, /oauth2/v2.0/token) are the unified Microsoft identity platform and use OIDC-style scopes (openid profile email User.Read). Mixing them produces obscure errors at the token exchange step.

// Microsoft Entra ID v2.0 — token exchange
const tokenRes = await fetch(
  `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,
  {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      client_id: process.env.MS_CLIENT_ID,
      client_secret: process.env.MS_CLIENT_SECRET,
      code,
      redirect_uri: process.env.MS_REDIRECT_URI,
      grant_type: "authorization_code",
      scope: "openid profile email User.Read",
    }),
  }
);

Microsoft also requires the redirect URI to be registered under the Web platform in the Entra ID app registration, not Single-page application or Mobile and desktop applications. Base44 functions are server-side; SPA platform configurations explicitly disable the client_secret and the token exchange will fail.

Slack OAuth v2

Slack's v2 OAuth (/oauth.v2.access) is the current API and the only one you should use for new integrations. The v1 endpoint (/oauth.access) still works but returns a different response shape and uses different scope syntax. If your token-exchange code was copied from a tutorial written before 2020, it likely uses v1.

The v2 response shape separates bot tokens from user tokens:

{
  "ok": true,
  "access_token": "xoxb-bot-token",
  "scope": "chat:write,channels:read",
  "bot_user_id": "U0KRQLJ9H",
  "team": { "id": "T9TK3CUKW", "name": "Slack Pickleball Club" },
  "authed_user": {
    "id": "U1234",
    "access_token": "xoxp-user-token",
    "scope": "identity.basic,identity.email"
  }
}

If you want a bot token (for posting to channels), read access_token at the top level. If you want a user token (for reading the user's profile), read authed_user.access_token. The two are governed by separate scope parameters in the authorization URL — scope for bot and user_scope for user — and Slack will silently return a token in only the field corresponding to the scopes you requested.

For pure sign-in flows, you usually want the user scopes identity.basic identity.email identity.avatar and you read authed_user.access_token followed by https://slack.com/api/users.identity.

Generic OIDC providers

For any provider that implements OpenID Connect (Okta, Auth0, Keycloak, Authelia, custom Hydra deployments), the flow is standardized and the discovery document tells you the endpoints:

// Discover OIDC configuration
const discovery = await fetch(
  `${issuer}/.well-known/openid-configuration`
).then((r) => r.json());
// discovery.authorization_endpoint
// discovery.token_endpoint
// discovery.userinfo_endpoint
// discovery.jwks_uri

Use the discovery document. Do not hardcode endpoints. Providers move them.

// Generic OIDC token exchange with PKCE
const tokenRes = await fetch(discovery.token_endpoint, {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code,
    redirect_uri: process.env.OIDC_REDIRECT_URI,
    client_id: process.env.OIDC_CLIENT_ID,
    client_secret: process.env.OIDC_CLIENT_SECRET,
    code_verifier: storedVerifierForState(state),
  }),
});
const { access_token, id_token } = await tokenRes.json();

The two requirements that almost-but-not-quite-standard providers miss: the code_verifier is a string, not the SHA-256 hash, and the redirect_uri must be byte-identical to the one in the authorization URL.

Custom OAuth providers you built yourself

If your provider is something your own team built (a partner SSO, a legacy auth system you wrapped in an OAuth layer), the failure mode is usually the state parameter or the nonce. The OAuth 2.0 spec says state is recommended; OIDC says nonce is required for the implicit and hybrid flows. Many home-grown providers do not enforce these and many home-grown clients do not send them. The result works in development and fails when you turn on strict mode in production.

Always: generate a cryptographically random state per authorization request, store it server-side keyed to the user's session, validate the returned state matches, and reject the callback if it does not.

How to confirm base44 third-party OAuth is broken (reproduction)

  1. Open your Base44 app in a fresh incognito window so there is no cached session.
  2. Open browser DevTools, switch to the Network tab, and check "Preserve log."
  3. Click the sign-in-with-provider button. Watch the redirect to the provider.
  4. If you land on a provider error page (GitHub: an invalid_request page; Microsoft: a login.microsoftonline.com error page; Slack: a slack.com consent failure), step 1 is wrong. Copy the full URL of the error page and the error_description query parameter. The provider tells you exactly what is wrong with the authorization URL.
  5. If you complete the consent screen and land back on your Base44 app with a query parameter like ?error=access_denied&error_description=..., the user denied consent or your client is misconfigured. Read the description.
  6. If you land back on your Base44 app with ?code=abc&state=xyz but the page shows an error or redirects to login, step 4 or 5 is wrong. Open the Base44 function logs for your OAuth callback handler. Find the request and read the full log line — the token-exchange response and any thrown exception should be there.
  7. Capture the registered redirect URI from the provider's app settings. Capture the actual callback URL from the failed request in step 6. Diff them character by character. If they differ at all, the provider rejected the token exchange.

How to fix base44 third-party OAuth — by failing step

Open the provider's app registration and verify the redirect URI is registered exactly as Base44 is sending it. Open your authorization URL builder and confirm every required parameter is present. For Microsoft, double-check the tenant in the URL path. For Slack, confirm the scopes are spelled exactly as in the v2 scope reference. Rebuild and retry.

If step 3 fails (callback never fires or fires with error)

Provider audit logs are your friend here. GitHub: Settings -> Developer settings -> OAuth Apps -> your app -> Advanced -> Recent revocations and authorizations. Microsoft: Entra ID -> Sign-in logs. Slack: api.slack.com/apps -> your app -> Activity logs. The provider records why it rejected the flow.

If step 4 fails (token exchange returns an error)

The token-exchange response always includes an error and error_description field. Common values: invalid_grant (the code is wrong, used, or expired — codes are single-use and expire in ~10 minutes), invalid_client (client_id or client_secret is wrong), redirect_uri_mismatch (the redirect_uri in the POST does not match the one in the authorization URL or the registered URI), unsupported_grant_type (you sent grant_type=code instead of grant_type=authorization_code).

If step 5 fails (profile fetch succeeds but session create errors)

Log the full profile response. Map every field your session create reads to a field the provider actually returns. Most often, you assumed email and the provider returned mail (Microsoft Graph), or you assumed id and OIDC returned sub. Add an explicit mapping layer between the provider response and your user model.

In all cases: enable verbose logging on the callback handler

console.log("oauth callback", {
  provider: "github",
  code: code ? "present" : "missing",
  state: state ? "present" : "missing",
  query_keys: Object.keys(req.query),
});

Log enough to diagnose; never log the code itself, the access token, or the client secret. These end up in your function logs and become a credential leak.

How long does it take to fix base44 third-party OAuth?

For a single-provider integration where you have access to the provider's app settings and your Base44 function code, plan 30-60 minutes once you have isolated the failing step. The longest part is usually waiting for the provider's audit log to update or for a propagation delay after you change the registered redirect URI.

For a multi-provider integration where several providers are partially broken, plan 2-4 hours. Fix one provider end-to-end before moving to the next; trying to fix three at once produces a soup of half-applied configurations and you cannot tell which change helped.

Migrating from a custom OAuth provider to a standard one (replacing a home-grown OAuth wrapper with Auth0 or Clerk) is a 1-3 day project depending on how deeply the custom provider's token format is wired into your code.

DIY vs hire decision

DIY this if: You have access to both the Base44 function code and the provider's app settings, you can read function logs, and the failure is on a single provider. Most third-party OAuth fixes are a 5-line change once you know which line is wrong.

Hire help if: The integration touches a partner SSO where you do not own the provider, the failure is intermittent (works for some users and not others, which usually means a tenant or scope edge case), or you need the fix shipped before a launch and you cannot afford a half-day diagnostic loop. Our fix-sprint handles a single broken OAuth integration end-to-end including the audit log review and the production verification.

Need this fixed before your next launch?

Our fix-sprint ships a working OAuth integration for any single provider including the provider-side app configuration review, the callback handler rewrite, PKCE adoption if missing, structured logging instrumentation, and an end-to-end verification in production.

Start a fix-sprint for a broken OAuth integration

QUERIES

Frequently asked questions

Q.01Why is my base44 github oauth not working?
A.01

Base44 GitHub OAuth most commonly breaks for three reasons. First, the OAuth App you created on GitHub has a different callback URL than Base44 is sending — GitHub requires an exact match including the protocol, host, port, and path. Second, you did not request the right scopes when you built the authorization URL. GitHub does not return an email address unless you include `user:email` or `read:user`, so your profile fetch returns null for email and your session create fails. Third, you are using a GitHub App (which uses installation tokens) when you meant to use a GitHub OAuth App (which uses user-to-server tokens). These are different products with different flows and they are easy to confuse.

Q.02Why does my base44 microsoft sso flow fail at the redirect?
A.02

Microsoft Entra ID (formerly Azure AD) OAuth fails at the redirect when the authorization URL uses the wrong tenant ID or the wrong v2.0 endpoint. The URL must be `https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize` where `{tenant}` is your directory ID, `common` for multi-tenant, `organizations` for any work account, or `consumers` for personal Microsoft accounts. Mixing v1.0 and v2.0 endpoints is the second common cause — the v1.0 token endpoint returns an ID token in a different format and the v2.0 userinfo endpoint will reject it. Third, the app registration must list your Base44 callback URL under platform `Web`, not `Single-page application`, because Base44 exchanges the code from a server function.

Q.03Why does my base44 slack oauth integration fail silently?
A.03

Slack OAuth fails silently when you mix v1 and v2 scopes. Slack's v2 OAuth (`/oauth.v2.access`) uses scope strings like `chat:write`, `channels:read`, and `users:read`, while the older v1 endpoint used different names. If you registered v2 scopes but your token-exchange code calls `/oauth.access` (the v1 endpoint), the response shape is different and your handler silently misses the `authed_user.access_token` field. The second silent failure mode is requesting a user token (`user_scope`) when you meant a bot token (`scope`) — Slack returns 200 with a token in the wrong field and your downstream API calls return `invalid_auth`. Log the full response from the token endpoint to disambiguate.

Q.04What does the base44 oauth callback error mean?
A.04

An OAuth callback error means the provider redirected back to your Base44 app with an `error` query parameter instead of a `code`. The most common values are `access_denied` (the user clicked deny on the consent screen), `invalid_request` (your authorization URL was malformed — usually a bad redirect_uri or missing required parameter), `unauthorized_client` (the client ID is wrong or the app is in test mode and the user is not a tester), and `invalid_scope` (you asked for a scope the provider does not recognize). The `error_description` query parameter usually has the real cause in human-readable form. Log both parameters in your callback handler before you redirect the user to an error page.

Q.05Do i need to use PKCE with my base44 custom oauth provider?
A.05

Yes, for any new OAuth integration in Base44 you should use Authorization Code with PKCE. PKCE (Proof Key for Code Exchange) was originally a mobile-app protection but the OAuth 2.1 draft and most modern providers require it for all clients, public or confidential. Generate a 43-128 character code_verifier per authorization request, send the SHA-256 hash as the code_challenge with method S256 in the authorization URL, store the verifier in a server-side session or signed cookie keyed by the state parameter, and include the verifier when you exchange the code for tokens. Skipping PKCE leaves you vulnerable to authorization code interception attacks even when you also use the client_secret.

Q.06How do i debug a base44 oauth integration that worked yesterday?
A.06

OAuth flows that worked yesterday and break today usually fail for one of four reasons. First, the client secret rotated — most providers let you create a new secret and revoke the old one, and a teammate may have done this. Second, the provider deprecated an endpoint or scope — Microsoft, Slack, and Google all retire v1 endpoints on published schedules. Third, your callback host changed — if Base44 reassigned a preview URL or you moved to a custom domain, the registered redirect URI no longer matches. Fourth, the user revoked consent — many providers expire consent after a period of inactivity, and the user has to re-consent. Check the provider's audit log first; it usually tells you which one.

NEXT STEP

Need this fix shipped this week?

Book a free 15-minute call or order a $497 audit. We will respond within one business day.