Social login
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.
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)
- Begin.
GET /api/v1/{module}/social/{provider}a providerhez tartozó goth-klienssel felépíti a consent URL-t, egy aláírtstateparaméterrel, és302-vel oda irányítja a böngészőt (gen.SocialBegin302Response+Locationheader). - Consent. A felhasználó a provider oldalán jóváhagyja a hozzáférést.
- Callback. A provider visszairányít a
/social/{provider}/callbackvégpontra:- a legtöbb provider (Discord, Google, Facebook) query-stringben (
?code=...&state=...) →GET,SocialCallbackQuery, - Apple form_post-ban (
application/x-www-form-urlencodedbody) →POST,SocialCallbackForm(lásd Apple-specifikumok).
- a legtöbb provider (Discord, Google, Facebook) query-stringben (
- A handler ellenőrzi a
state-et, becseréli acode-ot access tokenre, lekéri a provider profilját, ésgoth.User-ből normalizáltsocial.ExternalUser-t épít (Provider,ProviderKey,Email,Name,AvatarURL). - 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, azrbac/middleware réteg érintetlen marad.
Account-linking szabály
- Már ismert provider-identitás (
provider+providerKeyegyezik egy meglévőauth_credentialssorral): egyenes bejelentkezés,IssueTokensa 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 (
SocialEmailRequiredhiba,422): a kit megköveteli az emailt, mert ausers.emailaz 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
| Provider | goth csomag | Alap scope-ok | Callback |
|---|---|---|---|
| Discord | providers/discord | identify, email | GET (query) |
providers/facebook | email | GET (query) | |
providers/google | email, profile | GET (query) | |
| Apple | providers/apple | email | POST (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).
| Provider | Env-változók | Hol szerzed be |
|---|---|---|
| Discord | OAUTH_DISCORD_CLIENT_ID, OAUTH_DISCORD_CLIENT_SECRET | Discord Developer Portal, OAuth2 fül |
OAUTH_FACEBOOK_CLIENT_ID, OAUTH_FACEBOOK_CLIENT_SECRET | Meta for Developers, Facebook Login termék | |
OAUTH_GOOGLE_CLIENT_ID, OAUTH_GOOGLE_CLIENT_SECRET | Google Cloud Console, OAuth 2.0 Client ID | |
| Apple | OAUTH_APPLE_CLIENT_ID, OAUTH_APPLE_TEAM_ID, OAUTH_APPLE_KEY_ID, OAUTH_APPLE_PRIVATE_KEY | Apple 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.
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/namescope kérése Apple-nél automatikusanresponse_mode=form_post-ot kapcsol be a goth-ban: Apple acode/statepárostapplication/x-www-form-urlencodedbody-val POST-olja a callback URL-re, nem query-stringben. Ezért van különPOST /social/{provider}/callbackvégpont (SocialCallbackForm) a szokásosGET-es mellett; mindkettő ugyanazt asocial.Registry.Complete-et hívja. - Első consent. Apple csak az első jóváhagyáskor küldi el a nevet (egy
usermezőben, JSON-ként), ismételt bejelentkezésnél csak asub(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
Emailegy Apple-generált@privaterelay.appleid.comcí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: astateé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 azAPP_BASE_URL-ből számoltnál).422a 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
Identityfölötti szerep-/permission-ellenőrzés, változatlan social loginnál is. - Konfiguráció: a kit saját,
envPrefix-fel komponálható configjai (azOAUTH_*változók a generált projekt saját configja, nem itt élnek).