Supabase

Auth

The Supabase auth driver, the setup guard, and the invite acceptance page.

This layer plugs Supabase sign-in into the admin shell of @plutocms/core. Core owns the /admin route, the sign-in page, the route guard, and the navbar. This layer registers an authDriver and a few other registry entries. See Admin shell.

The driver

FieldValue
user()The Supabase user mapped from the JWT claims. The id is sub. The name is display_name, then full_name, then name, from user_metadata.
loggedIn()true when a Supabase session exists. The session cookie gives the same answer on the server and in the first client render, so there is no hydration mismatch.
ready()Resolves at once. The module plugin of @nuxtjs/supabase reads the session first.
refresh()Reads the session and the claims again.
signOut()Calls supabase.auth.signOut() and clears the session state.
signInThe email and password form AuthSignIn. It opens redirect, else /admin/home.
guard(to)The setup guard.
linksSign up and forgot password.
publicPathsSee the next section.

Public paths

PathA signed-in user
/admin/signupGoes to the admin home
/admin/forgot-passwordGoes to the admin home
/admin/confirmStays on the page. A confirm link signs the user in.
/admin/update-passwordStays on the page. A recovery link signs the user in.
/admin/accept-inviteStays on the page. An invite link signs the user in.
/admin/setupStays on the page. The setup guard decides.

The sign-up, forgot-password, update-password, and confirm pages use PlutoAdminAuthCard with the auth layout. Every auth page has the same frame as the sign-in page.

Accepting an invite

The page /admin/accept-invite is behind the invite email. The browser client uses PKCE. The default invite email gives tokens in the URL hash, which the PKCE client does not read. So the page reads the link itself.

LinkWhat the page does
?token_hash=...&type=inviteVerifies the one-time code as an invite
?token_hash=...&type=magiclink (or signup)Verifies the one-time code as a magic link
#access_token=...&refresh_token=...Sets the session from the tokens
#error_code=otp_expired (or any error_code)Shows "Link expired, ask for a new invite"
No token, but a sessionGoes on to the password form
NothingShows "Invalid link"

The page clears the URL. Then it shows a password form with a confirm field. It updates the password, refreshes the driver, and goes to /admin/home.

The setup guard

The driver guard(to) runs on /admin/**, after ready() and before the sign-in rule. A new install goes to /admin/setup and not to the sign-in page.

  • Setup not done, or the state cannot be read: every admin path but /admin/setup goes to /admin/setup.
  • Setup done: /admin/setup goes to /admin/login.
Keep the second rule. The setup page asks an anonymous visitor for a database connection string. It is safe only because it is unreachable after the setup.

A finished setup is cached, so the guard does not fetch on every navigation. A result that is not "done" is read again each time. The guard never rejects.

Site info

site.name() is the website_title setting. site.url() is the website_url setting. In development, url() is empty, so View site opens /.

The migrations banner is in adminBanners. Core's admin layout renders it. It hides on /admin/migrations.

Permissions and language

This layer has no sync plugin. The core plugin pluto-auth-sync.client.ts watches loggedIn. It loads the capabilities and the stored language after sign-in, and clears the capabilities after sign-out.

useAuth()

useAuth() has login, requestPasswordReset, and updatePassword. Use usePlutoAuth() from core for the user and the sign-out.

The sign-up page posts to POST /api/_pluto/supabase/auth/signup. The body has username (3 characters or more), display_name, email, and password (6 characters or more). A failed sign-up is an HTTP error with the key supabase.errors.signupFailed. A 4xx status from Supabase Auth keeps its status. Any other status becomes 502.

Migrate an older app

Before version 0.11, this layer had its own navbar, its own pages/admin.vue, its own sign-in page, and its own auth middleware. They are gone.

  • If you overrode NavbarAdmin or NavbarAdminProvider, delete the override. Add a link action to navbarActions, an item to accountMenu, or a banner to adminBanners.
  • If you overrode pages/admin.vue, delete the override. Move extra work to a banner or a plugin.
  • If you overrode pages/admin/(auth)/login.vue, delete it. Register another signIn component in an authDriver.
  • If you used the auth route middleware by name, remove the name.
  • useAuth() no longer returns isLoggedIn, user, logout, or allowedUnauthenticatedPaths. Use usePlutoAuth().
  • The supabase.navbar.* keys are removed. Core has pluto.navbar.*.
  • The layer depends on @plutocms/core. The package manager installs it with the layer. Do not add it to your app.

The old admin.vue showed a "not logged in" toast when the browser tab became visible with no session. Core's guard runs on navigation only, so this toast is gone.