OAuth2 Social Login¶
Add Google and GitHub login with a few config lines. Users can link multiple providers alongside email/password login.
Installation¶
Configuration¶
from fastapi_fullauth import FullAuth, FullAuthConfig
from fastapi_fullauth.oauth.google import GoogleOAuthProvider
from fastapi_fullauth.oauth.github import GitHubOAuthProvider
fullauth = FullAuth(
adapter=adapter,
config=FullAuthConfig(SECRET_KEY="..."),
providers=[
GoogleOAuthProvider(
client_id="your-google-client-id",
client_secret="your-google-secret",
redirect_uris=[
"http://localhost:3000/auth/callback",
"https://myapp.com/auth/callback",
],
),
GitHubOAuthProvider(
client_id="your-github-client-id",
client_secret="your-github-secret",
redirect_uris=["http://localhost:3000/auth/callback"],
),
],
)
Tip
redirect_uris is the list of allowed callback URLs. The client must pass redirect_uri as a query parameter in the authorize request = the library validates it against this list.
Routes¶
When OAuth providers are configured, these routes are registered automatically:
| Method | Path | Description |
|---|---|---|
| GET | /auth/oauth/providers |
List configured providers |
| GET | /auth/oauth/{provider}/authorize |
Get authorization URL |
| POST | /auth/oauth/{provider}/callback |
Exchange code for tokens |
| GET | /auth/oauth/accounts |
List linked OAuth accounts |
| DELETE | /auth/oauth/accounts/{provider} |
Unlink a provider |
How the flow works¶
1. Get the authorization URL¶
Response:
The redirect_uri parameter is optional. If omitted, the first URI in your redirect_uris list is used. The value is validated against the allowed list.
2. Redirect the user¶
Your frontend redirects the user to the authorization_url. The user authenticates with Google/GitHub.
3. Handle the callback¶
The provider redirects back to your redirect_uri with code and state query parameters. Your frontend sends these to the callback endpoint:
Response:
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "bearer",
"expires_in": 1800,
"user": { "id": "...", "email": "user@example.com", "is_active": true, "is_verified": true }
}
From this point on, the session works exactly like email/password login. The user can call /me, /refresh, /logout, etc. with the JWT tokens.
What happens on callback¶
- State token is verified (CSRF protection, 5-minute TTL)
- Authorization code is exchanged for provider tokens
- User info is fetched from the provider (email, name, picture)
- Account linking logic runs:
- If this provider account is already linked - update tokens, return existing user
- If an account with the same email exists and the provider confirms the email is verified - link the OAuth account to it
- If the email exists but the provider hasn't verified it - reject the login (security check)
- Otherwise - create a new user (marked as verified, since the provider confirmed their email)
- JWT tokens are issued (same as regular login)
Warning
Auto-linking only happens when the provider reports email_verified=True. This prevents an attacker from creating a provider account with a victim's email and hijacking their local account. If the provider hasn't verified the email, the user must log in with their existing credentials and link the provider manually.
Auto-linking by email¶
By default, if a user registers with user@example.com via email/password, then later logs in with Google using the same verified email, the accounts are linked automatically. Disable this with:
Security model¶
State token: the OAuth state parameter is a purpose-scoped JWT with a 5-minute TTL (OAUTH_STATE_EXPIRE_SECONDS). It prevents CSRF attacks on the callback endpoint. If the state token is missing, expired, or tampered with, the callback is rejected.
Redirect URI validation: the library validates the redirect_uri parameter against the provider's configured redirect_uris list. Mismatched URIs are rejected with a 400 error.
Token storage: provider access and refresh tokens are stored in the oauth_accounts table and updated on each login.
OAuth-only users¶
Users created through OAuth login have no password hash. They authenticate exclusively through their linked provider.
- To set a password later, use
POST /change-password. Thecurrent_passwordfield is not required for users without an existing password. - Unlinking a provider is blocked if it's the user's only login method (no password set, no other linked providers). The user must set a password first.
Unlinking providers¶
Users can unlink an OAuth provider:
This is blocked if the OAuth account is the user's only login method (no password set, no other OAuth providers). The user must set a password first.
Adding your own provider¶
Subclass OAuthProvider and implement three methods:
from fastapi_fullauth.oauth.base import OAuthProvider, OAuthUserInfo
class MyProvider(OAuthProvider):
name = "myprovider"
@property
def default_scopes(self) -> list[str]:
return ["openid", "email"]
def get_authorization_url(self, state: str, redirect_uri: str) -> str:
# build and return the provider's authorization URL
...
async def exchange_code(self, code: str, redirect_uri: str) -> dict:
# POST the code to the provider's token endpoint
# return {"access_token": "...", "refresh_token": "...", ...}
...
async def get_user_info(self, tokens: dict) -> OAuthUserInfo:
# fetch user info from the provider using the access token
return OAuthUserInfo(
provider=self.name,
provider_user_id="...",
email="user@example.com",
email_verified=True,
name="User Name",
picture=None,
raw={}, # full provider response
)
See GoogleOAuthProvider and GitHubOAuthProvider in the source for complete examples.
Event hooks¶
Two hooks fire during OAuth flows:
after_oauth_loginfires on every OAuth login (new and returning users):
async def on_oauth_login(user, provider, is_new_user):
if is_new_user:
print(f"New user via {provider}: {user.email}")
fullauth.hooks.on("after_oauth_login", on_oauth_login)
after_oauth_registerfires only when a new user is created via OAuth, and includes the provider's user info:
async def on_oauth_register(user, user_info):
print(f"New OAuth user: {user.email}, provider data: {user_info.raw}")
fullauth.hooks.on("after_oauth_register", on_oauth_register)
The after_register hook also fires for new OAuth users.
Provider setup guides¶
Google¶
- Go to Google Cloud Console
- Create a project (or select existing)
- Go to APIs & Services > Credentials
- Create an OAuth 2.0 Client ID (Web application)
- Add your redirect URIs under Authorized redirect URIs
- Copy the Client ID and Client Secret
Default scopes: openid, email, profile
GitHub¶
- Go to GitHub Developer Settings
- Click New OAuth App
- Set the Authorization callback URL to your redirect URI
- Copy the Client ID and Client Secret
Default scopes: read:user, user:email
Note
GitHub requires a separate API call to fetch the user's verified primary email. The library handles this automatically.