name: smbcloud-auth
description: Use when working on authentication in the sigit-si Rails app — sign in, sign up, profile (me), sign out, or account removal. sigit-si is a platform client ("sigit") that consumes smbCloud Auth as an auth-as-a-service layer through the smbcloud-auth Ruby gem. Covers the SmbcloudAuthService wrapper, the local User upsert, env/credential config, the native-extension build caveat, and the planned /api/v1 JSON surface for the siGit Code & Deploy desktop app.
smbCloud Auth in sigit-si
Product map: siGit Code (local agent) · siGit Code Cloud (hosted chat + Cloud Sessions) · siGit Code Cloud Agent (autonomous task → PR; planning).
sigit.siis Git hosting;code.sigit.siis the home of siGit Code. Full taxonomy: product-overview.
sigit-si does not own user identity. It is a platform client (client id
sigit) that authenticates against the shared smbCloud Auth service. Treat
smbCloud Auth as the source of truth for credentials; sigit-si keeps only a thin
local mirror of the user.
For the full auth model (platform clients vs tenant auth apps, the Rust/WASM
SDKs, the web console), see the authoritative skill in
smbcloud-cli/.agents/skills/smbcloud-auth/SKILL.md. This file is the
sigit-si-specific view.
Where things live
app/services/smbcloud_auth_service.rb— the only place that talks to the gem. Do not callSmbCloud::Authdirectly from controllers; go through this service.app/controllers/sessions_controller.rb— HTML sign in / sign out (/auth).app/controllers/registrations_controller.rb— HTML sign up (/auth/signup).app/controllers/oauth/provider_controller.rb— shared "Continue with " flow (/auth/<provider>→ smbCloud authorize;/auth/<provider>/callbackconsumes the returnedaccess_token). smbCloud brokers the provider OAuth flow; this app only starts it and reuses the sameme→ upsert → session path assessions#create. Thin subclasses:oauth/github_controller.rb,oauth/google_controller.rb. The SPA (code.sigit.si) equivalents live underapi/v1/oauth/(brokered_controller.rbbase + per-provider subclasses) with a redirect_uri allowlist. Adding a provider = two ~15-line subclasses +SmbcloudAuthService.<p>_authorize_url- routes + a button partial — provided smbcloud-api brokers it and the
AuthApp has the provider's OAuth client configured (e.g.
google_oauth_client_id/secreton the auth app; without it the broker returns "Google sign-in is not configured for this app").
- routes + a button partial — provided smbcloud-api brokers it and the
AuthApp has the provider's OAuth client configured (e.g.
app/models/user.rb— local mirror, upserted viaUser.find_or_create_from_smbcloud(profile.merge(access_token:)).Gemfile—gem "smbcloud-auth", "~> 0.4.5"(native Rust/Magnus extension).
SmbcloudAuthService surface
Class methods, all of which translate gem errors into local error types:
login(email:, password:)→access_token(String)me(access_token:)→ profile hash{ id:, email:, created_at:, updated_at: }signup(email:, password:)logout(access_token:)remove(access_token:)— deletes the smbCloud accountclient— memoizedSmbCloud::Authclient built from env config
Error contract
SmbcloudAuthService::AuthenticationError— base; carries optionalerror_codeSmbcloudAuthService::AccountNotFoundError— no account for that emailSmbcloudAuthService::AccountIncompleteError— account exists but not verified; surface the "check your email" message, do not treat as bad credentials- Underlying gem failures arrive as
SmbCloud::Auth::Error(witherror_code) and must be caught inside the service, never leaked to controllers.
Controllers map these to flash + HTTP status (:unprocessable_entity for auth
failures, :internal_server_error for config/unexpected). Preserve that mapping.
Local user mirror & identity reconciliation
app/models/user.rb mirrors the smbCloud account. The upsert keys on
smbcloud_id (smbCloud's numeric auth_user.id) but reconciles by email —
and that reconciliation is load-bearing, not a nicety:
smbCloud's
auth_user.idis per-AuthApp, not a stable global identity. The same email is a differentauth_user(different id) in every AuthApp, and the id also changes if the upstream account is deleted/recreated. So the moment sigit-si is repointed at a different AuthApp — e.g. moving the app to a different smbCloud account — every existing localUser.smbcloud_idgoes stale: it points at anauth_userthat no longer backs that email.
Because User.email is locally unique, a smbcloud_id-only upsert then breaks:
a login returns the email's current auth_user.id, no local row matches it,
and inserting a new row collides on the unique email →
ActiveRecord::RecordInvalid, which surfaces as a generic "An unexpected error
occurred" on sign-in (this is exactly how the GitHub callback failed at
launch — the GitHub login resolved to the email's current AuthApp auth_user,
which differed from the id stored when the app pointed at an earlier
AuthApp/account).
find_or_create_from_smbcloud therefore does: look up by smbcloud_id; if that
misses and the authenticated email already has a local user, adopt that row
and move it onto the new smbcloud_id. Treat email as the durable key and
smbcloud_id as a pointer that can change. Never reintroduce a
smbcloud_id-only upsert. (Specs: spec/models/user_smbcloud_spec.rb.)
Credentials & environment
Required env vars (raise KeyError if missing — handle as a config error, not an
auth failure):
SIGITSI_SMBCLOUD_APP_IDSIGITSI_SMBCLOUD_APP_SECRET
The smbCloud environment (dev vs production) is selected inside the service. Keep dev and production app credentials separate; never hardcode either.
Security note: sigit-si is a server-side confidential client, so holding
app_secret here is fine. This is different from the desktop/browser clients —
see the public-client rule in the smbcloud-cli skill before reusing this pattern
in sigit-app.
Native extension build caveat
smbcloud-auth compiles a Rust extension via magnus/rb_sys. The pinned
magnus version does not build against Ruby 4.0.x:
error[E0609]: no field `typed_flag` on type &rb_sys::RTypedData
error: could not compile `magnus`
This affects every published version of the gem, not a specific one. If a
bundle install fails to build the native extension on Ruby 4.0, the fix is in
the gem (bump magnus/rb_sys in its ext/auth/Cargo.toml and republish)
or build under Ruby 3.3.x — not in this repo's Gemfile.
macOS 26/27: dlopen rejects the built bundle (empty LC_ID_DYLIB)
On macOS 26/27, a source-gem install of smbcloud-auth builds fine but fails
at runtime with:
LoadError (dlopen(.../smbcloud-auth-X.Y.Z/lib/auth/auth.bundle):
load command #4 string extends beyond end of load command)
Every request that touches SmbcloudAuthService then 500s (it does
require "auth" at file-eval). Root cause: rb-sys's generated Makefile
(fixup_libnames in rb_sys/mkmf.rb) ends the build with
install_name_tool -id "" $(DLLIB), leaving an empty LC_ID_DYLIB install
name, which the stricter dyld on macOS 26/27 rejects. gem pristine does
NOT help — it rebuilds through the same broken step.
- Durable fix lives in the gem source (
smbcloud-clirepo, merged todevelopment2026-07-02):sdk/gems/auth/ext/auth/extconf.rbpost-processes the generated Makefile to stamp-id "@rpath/auth.bundle"(coversgem install), and the gem'sRakefiledoes the same forrake native gembuilds. Ships in the next gem release after 0.4.5. - Local band-aid if a broken bundle is already installed:
install_name_tool -id "@rpath/auth.bundle" <path>/auth.bundle
codesign -f -s - <path>/auth.bundle
- Diagnose with
otool -l auth.bundle | grep -A3 LC_ID_DYLIB— an emptyname (offset 24)is the broken state.
Planned: /api/v1 JSON auth for the desktop app
The "siGit Code & Deploy" Tauri app (sigit-app) needs a JSON, token-based auth
surface — the current controllers are HTML + session-cookie only. When adding
sigit.si/api/v1:
- add an
Api::V1controller namespace; reuseSmbcloudAuthService, do not re-implement gem calls - authenticate requests with the smbCloud
access_token(bearer), not the Rails session cookie - return JSON shapes that match the desktop client's
account/command_*set: login, signup, me, logout, remove_account, resend confirmation, reset password, check email/oauth - keep the HTML controllers working; the API is additive (migrating the HTML flows onto the API is optional, not required)
- keep
sigit-appaccount features at parity with the sigit-si web account page
Validation
bundle exec rails routes | grep -E 'auth|api/v1'ruby -con touched controllers/servicesbundle exec rails db:migrateif the localUserschema changed- exercise sign in / sign out / sign up against the dev smbCloud environment
Common mistakes
- Calling
SmbCloud::Authdirectly from a controller instead of via the service - Keying the local
Userupsert only onsmbcloud_id— it is per-AuthApp and changes when the app is repointed at a different AuthApp/account (or an upstream account is recreated); reconcile by email - Treating
AccountIncompleteErroras bad credentials instead of "verify email" - Leaking
SmbCloud::Auth::Error(or itserror_code) to the view layer - Using the Rails session cookie to authenticate desktop-app API requests
- Assuming a gem version bump caused a native build failure that is really the Ruby 4.0 / magnus incompatibility
- Reusing the server-side
app_secretpattern in a public client (desktop/browser)