A kit

Social login

Discord, Facebook, Apple, Google és bármely más OAuth2 provider bekötése az add auth modulba.

Add meg a --social flaget az add auth-nak (lásd a CLI áttekintőt), ha a usereknek egy külső identity provideren keresztül is be kell tudniuk lépni, az email+jelszó mellett vagy helyett. Ez az oldal azt írja le, mi generálódik, és hogyan köss be egy providert a négy beépítettön túl.

go tool gpsystem add auth --social discord,facebook,apple,google

Az add auth (lásd a CLI áttekintőt) mindig generál egy provider-agnosztikus social login réteget a modulba: egy markbates/goth-ra épülő providerregisztert, két végpontot (GET /social/{provider} és GET/POST /social/{provider}/callback), és egy LoginWithProvider service-metódust, ami ugyanazt a token-kibocsátást használja, mint az email+jelszavas login. A --social flag csak azt dönti el, mely négy ismert providert (Discord, Facebook, Apple, Google) vezeti be előre konfigurálva; bármelyik másik goth-provider (vagy egy kézzel írt goth.Provider) egy sornyi kézi hozzáadás a generált social/providers.go-ban.

A goth a generált projekt függősége, nem a kité: a kit auth/ csomagja szándékosan stateless (csak JWT sign/parse + middleware), a --social-lal bekapcsolt kód a projekt saját go.mod-jába kerül (go mod tidy húzza be). Ez a döntés a research alapján született: a kit egy modul, és egy OAuth-könyvtárnak nincs helye benne, ha a generált auth modul amúgy is a projektben él (bcrypt, refresh-rotáció, email-flow-k, mind ott).

Miért ez az architektúra

A users/auth_credentials séma már az első naptól multi-providerre készült: az auth_credentials.provider + provider_key pár (UNIQUE (user_id, provider)) egy local (bcrypt hash) sort tárol email+jelszavas regisztrációnál, egy social login ugyanide egy discord/google/... sort ír a provider stabil külső user-id-jával. A LoginWithProvider erre a sémára épül: nincs külön "social user" tábla, egy user több providerrel is beléphet.

A generált strict-server réteg (TypeSpec → OpenAPI → oapi-codegen) miatt a handler-metódusok szignatúrája func(ctx context.Context, req ...) (Resp, error): nincs közvetlen hozzáférés a net/http kéréshez, tehát cookie-t sem lehetne kényelmesen beállítani a begin és a callback között. Emiatt a state-paraméter önmagát ellenőrző, aláírt token (lásd lejjebb), nem szerveroldali session: a folyamat így teljesen stateless marad, ugyanabban a szellemben, mint a kit auth/ csomagja.

A login-flow

sequenceDiagram
    participant B as Böngésző
    participant A as API (generált auth modul)
    participant P as Provider (pl. Discord)

    B->>A: GET /api/v1/auth/social/discord
    A->>A: aláírt state generálása (HMAC, JWT_SECRET-tel)
    A-->>B: 302 Location: provider consent URL (state=...)
    B->>P: consent screen
    P-->>B: redirect /callback?code=...&state=...
    B->>A: GET /api/v1/auth/social/discord/callback
    A->>A: state ellenőrzése, code becserélése access tokenre
    A->>P: GET /users/@me (Bearer access token)
    P-->>A: profil (email, name, avatar, user id)
    A->>A: find-or-create/link + IssueTokens
    A-->>B: 200 TokenPair (accessToken, refreshToken, user)
  1. Begin. GET /api/v1/{module}/social/{provider} a providerhez tartozó goth-klienssel felépíti a consent URL-t, egy aláírt state paraméterrel, és 302-vel oda irányítja a böngészőt (gen.SocialBegin302Response + Location header).
  2. Consent. A felhasználó a provider oldalán jóváhagyja a hozzáférést.
  3. Callback. A provider visszairányít a /social/{provider}/callback végpontra:
    • a legtöbb provider (Discord, Google, Facebook) query-stringben (?code=...&state=...) → GET, SocialCallbackQuery,
    • Apple form_post-ban (application/x-www-form-urlencoded body) → POST, SocialCallbackForm (lásd Apple-specifikumok).
  4. A handler ellenőrzi a state-et, becseréli a code-ot access tokenre, lekéri a provider profilját, és goth.User-ből normalizált social.ExternalUser-t épít (Provider, ProviderKey, Email, Name, AvatarURL).
  5. A service LoginWithProvider-je find-or-create/link logikát futtat (lásd lent), majd a modul meglévő core.Service.IssueTokens-ét hívja: ugyanaz a JWT/refresh-pár jön ki, mint email+jelszavas loginnál, az rbac/middleware réteg érintetlen marad.

Account-linking szabály

  • Már ismert provider-identitás (provider + providerKey egyezik egy meglévő auth_credentials sorral): egyenes bejelentkezés, IssueTokens a meglévő userre.
  • Első social login erre az identitásra, de az email egy meglévő, verifikált userhez tartozik: az új provider-credential hozzálinkelődik a meglévő userhez (a user innentől email+jelszóval és a social providerrel is bejelentkezhet).
  • Az email egy meglévő, DE nem verifikált userhez tartozik: elutasítva (409 Conflict). Egy nem verifikált email nem bizonyítja a tulajdonjogot; automatikus linkelés itt lehetővé tenné, hogy egy támadó ugyanazzal az email-string-gel regisztrált social accounttal átvegye az áldozat még nem verifikált local regisztrációját.
  • Az email egyáltalán nem ismert: új, előre verifikált user jön létre (a provider már igazolta az email-birtoklást, ezt a kit elfogadja).
  • A provider nem ad vissza emailt (SocialEmailRequired hiba, 422): a kit megköveteli az emailt, mert a users.email az account kulcsa. Emiatt minden beépített provider-regisztráció explicit email-scope-ot kér (lásd lent).

A négy beépített provider

Providergoth csomagAlap scope-okCallback
Discordproviders/discordidentify, emailGET (query)
Facebookproviders/facebookemailGET (query)
Googleproviders/googleemail, profileGET (query)
Appleproviders/appleemailPOST (form_post)

Provider-oldali beállítás és env-változók

Minden nem-Apple providernél egy OAuth2 app kell a providernél, a redirect/callback URL-lel {APP_BASE_URL}/api/v1/{module}/social/{provider}/callback (pl. https://app.example.com/api/v1/auth/social/discord/callback).

ProviderEnv-változókHol szerzed be
DiscordOAUTH_DISCORD_CLIENT_ID, OAUTH_DISCORD_CLIENT_SECRETDiscord Developer Portal, OAuth2 fül
FacebookOAUTH_FACEBOOK_CLIENT_ID, OAUTH_FACEBOOK_CLIENT_SECRETMeta for Developers, Facebook Login termék
GoogleOAUTH_GOOGLE_CLIENT_ID, OAUTH_GOOGLE_CLIENT_SECRETGoogle Cloud Console, OAuth 2.0 Client ID
AppleOAUTH_APPLE_CLIENT_ID, OAUTH_APPLE_TEAM_ID, OAUTH_APPLE_KEY_ID, OAUTH_APPLE_PRIVATE_KEYApple Developer, Sign in with Apple + Keys

Ezek a változók csak akkor kötelezők (env:"...,required"), ha a megfelelő --social flag szerepelt az add auth híváskor: a platform config csak a ténylegesen bekapcsolt providerek mezőit kapja meg.

Az OAUTH_APPLE_PRIVATE_KEY egy PKCS8 PEM privát kulcs (a "Sign in with Apple" kulcs letöltött .p8 fájljának tartalma). A generált providers.go ebből, a team id-ból és a key id-ból minden induláskor JWT-t gyárt (apple.MakeSecret): Apple-nél ugyanis nincs statikus client secret, csak egy legfeljebb 6 hónapig érvényes, aláírt token. Ha a kulcs formátuma hibás, a RegisterProviders hibát ad, amit a generált Register{{Surface}}panic-kal jelez indításkor (szándékosan: ez konfigurációs hiba, nem futásidejű állapot).

Apple-specifikumok

  • form_post callback. Az email/name scope kérése Apple-nél automatikusan response_mode=form_post-ot kapcsol be a goth-ban: Apple a code/state párost application/x-www-form-urlencoded body-val POST-olja a callback URL-re, nem query-stringben. Ezért van külön POST /social/{provider}/callback végpont (SocialCallbackForm) a szokásos GET-es mellett; mindkettő ugyanazt a social.Registry.Complete-et hívja.
  • Első consent. Apple csak az első jóváhagyáskor küldi el a nevet (egy user mezőben, JSON-ként), ismételt bejelentkezésnél csak a sub (stabil user id) és (ha engedélyezett) az email jön. A generált kód emiatt a névre fallbackként az emailt használja, ha a profil nem ad nevet.
  • Privát relé email. Ha a felhasználó az "Anonymize My Email" opciót választja, az Email egy Apple-generált @privaterelay.appleid.com cím lesz, ami stabil marad ugyanahhoz a userhez, de nem a valós címe.

Kézi provider hozzáadása

A generikus réteg (internal/modules/{module}/social/social.go) nem változik: minden bővítés a providers.go-ban, a // gpsystem:social-providers jelölő alatt történik.

Egy másik goth-provider (a ~60 beépített közül, pl. GitLab):

// providers.go
import "github.com/markbates/goth/providers/gitlab"

func RegisterProviders(reg *Registry, cfg Config) error {
    // gpsystem:social-providers
    if cfg.GitLab.ClientID != "" {
        reg.Register("gitlab", gitlab.New(cfg.GitLab.ClientID, cfg.GitLab.ClientSecret, callbackURL(cfg, "gitlab")))
    }
    // ...
}

Ehhez a Config-ba (config.go) egy GitLab ProviderConfig mezőt, a platform configba (config.go, // gpsystem:config anchor) egy OAUTH_GITLAB_CLIENT_ID/_SECRET párt, a Dependencies-be (register.go) és a Register{{Surface}} social.Config{...} literálba a megfelelő mezőket kell felvenni: pontosan ugyanazok a helyek, amiket a --social flag a beépített négynél generál.

Egy providernek, aminek nincs goth csomagja: implementáld a goth.Provider interfészt (BeginAuth, UnmarshalSession, FetchUser, Name/SetName, Debug, RefreshToken/RefreshTokenAvailable) egy saját típuson, majd reg.Register("name", &MyProvider{...}) ugyanígy.

Hibakeresés

  • 404 unknown social provider: a {provider} path-szegmens nincs regisztrálva (se --social-lal, se kézzel), vagy elgépelted a nevet.
  • 401 social login failed: a state érvénytelen (lejárt, 10 percnél régebbi, vagy nem erre a providerre szólt) vagy a code-csere hibázott a providernél (leggyakrabban: eltérő redirect URI a providernél és az APP_BASE_URL-ből számoltnál).
  • 422 a callbacknél, SocialEmailRequired: a provider nem adott vissza emailt, ellenőrizd, hogy a scope-ok között szerepel-e az email (a beépített négynél alapból igen).
  • 409 Conflict: az email egy meglévő, de nem verifikált userhez tartozik; a felhasználónak előbb a saját email+jelszavas regisztrációját kell verifikálnia (lásd Autentikáció).

Kapcsolódó oldalak

  • Autentikáció: a kit auth/ csomagja, JWT-kibocsátás, middleware.
  • RBAC: a tokenből lett Identity fölötti szerep-/permission-ellenőrzés, változatlan social loginnál is.
  • Konfiguráció: a kit saját, envPrefix-fel komponálható configjai (az OAUTH_* változók a generált projekt saját configja, nem itt élnek).
Copyright © 2026