Authentication
Every project ships GoTrue-based auth: email/password by default, optional Google and Apple sign-in, and JWT sessions that flow straight into Postgres RLS via auth.uid(). The portal's Authentication section covers users, sign-in providers, redirect URLs, and the confirmation and password-reset emails and pages — all editable per project, no redeploy needed.
| Surface | Availability |
|---|---|
| Portal UI | Users/Invite, Providers, URL Configuration, Confirmation, Reset page — full management |
| CLI | None — there's no shovelbase auth command; script user management via the Management API instead |
| SDK / HTTP | shovelbase.auth.* — sign up/in/out, sessions, and the admin API on a service-key client |
Authentication is the classic thing not to build yourself. Not because the happy path is hard — checking a password hash is twenty lines — but because the rest isn't: secure password storage, session expiry and refresh, email confirmation, password reset tokens that can't be replayed, OAuth callback handling. Each is a well-known place to get security subtly wrong, and none of it is what makes your product worth using.
What makes this integration more than a login box is where the session lands. Signing in returns a JWT carrying the user's id, the SDK attaches it to every subsequent request, and Postgres reads it back as auth.uid() inside your RLS policies. So identity and authorization are the same fact: the same token that proves who someone is also decides which rows they can see, with no separate permission layer to keep in sync.
Practical consequence — auth.users is managed by the auth service, so put your own profile fields in your own table keyed by the user id, rather than trying to extend it.
- Ordinary email/password sign-up
- Enabled by default. Turn on confirmation if you need verified addresses; leave it off while prototyping so sign-ups return a session immediately.
- Sign in with Google
- Lower signup friction and no password to reset. Needs an OAuth client from the Google Cloud console — the client ID and secret go in the portal, and the callback URL it shows you goes back to Google.
- Sign in with Apple
- Required by App Store review once an iOS app offers another social login. Needs a services ID and a signing key from the Apple developer portal; the portal mints the client secret Apple expects from that key.
- Invite-only products
- Internal tools and B2B betas where self-serve signup isn't wanted: create accounts with Invite user and leave sign-up unused.
- Per-user data without app-side checks
- Default a
user_idcolumn toauth.uid()and let a policy compare against it — the client never sends, and cannot forge, its own identity.
Users & Invite user
The Users page lists everyone in auth.users, with the sign-in providers each account uses, and lets you ban/unban or delete an account. There is deliberately no “add a user with a password you typed in” button — accounts are created by someone signing up, or by Invite user, which sends a sign-up email from the portal and builds the invite link from the Site URL (below). Scripting the same thing means POST /api/projects/<ref>/auth/users/send with { type: "invite" }, or the SDK's admin API on a service-key client.
Providers
Email / password is always enabled. Google and Apple are opt-in: supply the provider's credentials, and point its authorized redirect URI at the callback URL the portal shows you. The steps to get those credentials are below.
Sign in with Google
A one-time setup in the Google Cloud console, about ten minutes. Nothing is installed and no app code changes — you are creating an OAuth client at Google, then handing its credentials to the project.
- Toggle Google provider on. In the portal, open Authentication → Providers → Google and switch it on — the form expands with a Callback URL field and a copy button, already filled in as
https://<ref>.shovelbase.com/auth/v1/callback. A custom domain is optional here, but not required. Without one, Google's consent screen shows the user “to continue to shovelbase.com” rather than your project or product name — a custom domain replaces that with your own. - Create or pick a Google Cloud project. At console.cloud.google.com, use the project dropdown in the top bar → New Project, or select an existing one. Make sure it stays selected for the next two steps.
- Configure the OAuth consent screen. Under APIs & Services → OAuth consent screen (newer consoles: Google Auth Platform → Branding). On a new project this reads Google Auth Platform not configured yet — click Get started and fill in the app name and user support email, then pick External as the audience, unless you are on Workspace and only want accounts in your own organization, in which case pick Internal. Add a developer contact email, agree to the terms, and click Create.
- Add the scopes under Data Access. Open the Data Access tab and click Add or remove scopes. Search for and check these three:Click Update in the scope picker, then Save on the Data Access tab.
Scope What it's for openidAssociate you with your personal info on Google .../auth/userinfo.emailSee your primary Google Account email address .../auth/userinfo.profileSee your personal info, including any personal info you've made publicly available - Create the OAuth client. Open the Clients tab and click Create ClientMake sure to backup the Client ID and Client Secret as they can't be viewed again.
Application type You provide You get Web application Your Callback URL from step 1 into Authorized redirect URIs Client ID and client secret iOS Your app's bundle ID Client ID only - Save the client creds in the portal. Back on Authentication → Providers → Google, paste the client ID and secret, and Save. Only the web client's secret goes in the portal; if you add an iOS client, its client ID is appended to the same comma-separated Client IDs field. Enable Skip nonce check if you are using in an iOS app.
- Add yourself as a test user. Open the Audience tab and add your own email under Test users — while the app is in Testing, only accounts on that list can complete sign-in.
- Test the flow from your app.sign in with Googleconst { data, error } = await shovelbase.auth.signInWithOAuth({provider: "google",options: { redirectTo: "https://yourapp.com/welcome" },});
The
redirectTotarget must be the Site URL or on the additional redirect URLs allow-list under URL Configuration, or the redirect is dropped after an otherwise successful sign-in.When it fails, Google's error names the cause:
Error Cause redirect_uri_mismatchWhat you registered when creating the OAuth client isn't character-for-character the callback URL from step 1. access_blockedThe consent screen is still in Testing and the account isn't on its test-user list — add it under Audience, or publish the app. - Publish the app. Once you're satisfied with the behavior, go back to the Audience tab and click Publish app — with only these three non-sensitive scopes it goes live immediately, no verification queue, and any Google account can sign in from then on.
Sign in with Apple
Same shape as Google with one difference worth knowing up front: Apple issues no static client secret. The secret is a token you sign yourself with a .p8 key, and it expires after six months. The portal generates and regenerates it for you — you supply the key once.
Everything on Apple's side happens under Certificates, Identifiers & Profiles. It helps to know what these identifiers mean before you start:
| Value | Where it comes from | What it's for |
|---|---|---|
| App ID / Bundle ID | Identifiers → an App IDs entry you create | The identifier for your iOS or web application. Not entered in the portal, but the services ID and the signing key are both configured against it |
| Team ID | Top right of the developer account, under your team name | Associates the application with your Apple developer account |
| Services ID | Identifiers → a Services IDs entry you create | Identifies the authenticating service — in this case shovelbase |
| Key ID | Keys → the Sign in with Apple key you create | Tells Apple which key signed the secret |
| .p8 private key | Downloaded once when that key is created | Signs the secret |
- Toggle Apple provider on. In the portal, open Authentication → Providers → Apple and switch it on. The form expands with a Return URL ready to copy, already filled in as
https://<ref>.shovelbase.com/auth/v1/callback. - Enable Sign in with Apple on an App ID. Open developer.apple.com → Certificates, Identifiers & Profiles → Identifiers. What you do there depends on whether the app already exists:Whichever route you took, that identifier is the app's bundle ID — an iOS app you build later has to ship under exactly this string, so pick it with that in mind rather than as a throwaway.
Situation What to do You already have an app Click its App ID in the list, find Sign in with Apple in Capabilities, tick it, and Save. New app, or web only Click + → App IDs → Continue → App → Continue. Add a description and an explicit Bundle ID ( com.example.app), tick Sign in with Apple in Capabilities, then Continue → Register. - Register a Services ID. Back on Identifiers, click + again and choose Services IDs → Continue. The identifier has to differ from the App ID — the convention is to suffix it, e.g.
com.example.app.signin. Continue → Register. This is the value the portal calls Services ID. - Point the Services ID at your project. Open the services ID you just created, tick Sign in with Apple, and click Configure. In the sheet:Then Next → Done → Continue → Save. That last Save on the identifier page is easy to miss, and without it the configuration is discarded. Apple checks the domain resolves, so the project needs to be reachable before this step. If it asks you to verify the domain, it offers an
Field Value Primary App ID The App ID from step 2 Domains and Subdomains <ref>.shovelbase.com, or your custom domain — host only, nohttps://and no pathReturn URLs The Return URL copied in step 1, in full apple-developer-domain-association.txtdownload — publish that under/.well-known/on the project's website and hit verify. - Create the signing key. Go to Keys in the left sidebar and click +. Name the key, tick Sign in with Apple, click its Configure button and select the same primary App ID, then Save → Continue → Register. The page that follows shows the Key ID and a Download button for
AuthKey_<keyid>.p8. Apple lets you download it exactly once — save it somewhere you can find again, because a lost key can only be revoked and replaced. Note the Key ID before you leave the page (it's also listed under Keys afterwards). - Grab the Team ID. It's the ten-character string in the top right of the developer account, under your team name — also shown as Team ID under Membership details.
- Fill in the portal and generate the secret. Back on Authentication → Providers → Apple, open Signing details under the client secret and fill in the Team ID, Services ID and Key ID, then paste the contents of the
.p8file into the private key box — those four are exactly what the secret is signed from, and nothing else reads them. Click Generate: the portal signs the client secret Apple expects and shows its expiry, but changes nothing until you press Save changes. - Add native apps, if you have them. iOS and macOS apps sign in with their bundle ID, not the services ID — they present an ID token instead of running the web code exchange, so they never use the client secret. List those bundle IDs, comma-separated, in the Bundle IDs field.
- Test the flow from your app. On the web this is the ordinary OAuth redirect; on iOS, pass the credential from
ASAuthorizationControllerstraight tosignInWithIdTokenso the user never leaves the app.sign in with Apple// Webconst { data, error } = await shovelbase.auth.signInWithOAuth({provider: "apple",options: { redirectTo: "https://yourapp.com/welcome" },});// iOS (Swift) — credential from ASAuthorizationAppleIDCredentiallet session = try await shovelbase.auth.signInWithIdToken(credentials: .init(provider: .apple, idToken: idToken, nonce: nonce))
When it fails, the cause is usually one of these:
| Error | Cause |
|---|---|
invalid_client | The client secret expired, or it was generated for a different services ID or team. Open the provider and Regenerate, then Save. |
invalid_request | The Return URL isn't registered character-for-character on the services ID, or its domain hasn't been verified by Apple. |
bad ID token from a native app | The app's bundle ID isn't in the Bundle IDs field, or the nonce it was issued with isn't the one passed to signInWithIdToken. |
Apple caps the client secret's lifetime at six months, so this one needs a calendar entry that Google's doesn't. The provider card shows the expiry date and starts warning 30 days out; Regenerate re-signs it from the stored key — no second trip to the developer portal — and Save restarts auth with the new secret. Sessions already issued are unaffected either way.
URL Configuration
Sets the Site URL used to build {{ .ConfirmationURL }} for invite emails, plus an additional redirect URLs allow-list for OAuth callbacks. Password reset and confirm-signup have their own dedicated links, configured on the Reset page and Confirmation pages below.
Confirmation
A toggle for whether new sign-ups must confirm their email before they can sign in, an editable confirmation email (subject + HTML body, with a live preview), and the hosted confirmation page's own configuration.
Confirmation requires both an outbound SMTP relay on the project and this toggle switched on. Without a relay — or with the toggle off — new sign-ups are auto-confirmed and signUp() returns a usable session straight away, which is what makes it easy to prototype before wiring up email. Passwords have a minimum length of 8 characters.
Reset page
Password resets work without any app code: auth.resetPasswordForEmail(email) sends the editable recovery email (same subject/HTML-body editor, with a {{ .ConfirmationURL }}-style variable list), whose link opens a hosted page at https://<ref>.shovelbase.com/auth/reset. That page's HTML/CSS is fully yours to edit in the portal — style it freely as long as you keep the required element ids the portal lists (it wires the reset logic to them on serve); a {{ project_name }} variable is substituted in, and a Preview button shows the form without a real token.
Both email editors work — and their Preview buttons render — even before an outbound SMTP relay is configured on the project; they just won't actually send until one is.
HTTP API (/auth/v1 — GoTrue)
# Sign upcurl -X POST "https://<ref>.shovelbase.com/auth/v1/signup" \ -H "apikey: $ANON_KEY" -H "Content-Type: application/json" \ -d '{"email": "me@example.com", "password": "secret123"}' # Sign in → access_token (a user JWT for authenticated requests)curl -X POST "https://<ref>.shovelbase.com/auth/v1/token?grant_type=password" \ -H "apikey: $ANON_KEY" -H "Content-Type: application/json" \ -d '{"email": "me@example.com", "password": "secret123"}'See shovelbase-js for the same operations through shovelbase.auth.