# plain.oauth **Let users log in with OAuth providers like GitHub, Google, and more.** - [Overview](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#overview) - [Creating a provider](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#creating-a-provider) - [Connecting and disconnecting accounts](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#connecting-and-disconnecting-accounts) - [Using saved access tokens](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#using-saved-access-tokens) - [Customizing the provider](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#customizing-the-provider) - [Settings](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#settings) - [FAQs](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#faqs) - [How is this different from other OAuth libraries?](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#how-is-this-different-from-other-oauth-libraries) - [Why are providers not included in the library?](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#why-are-providers-not-included-in-the-library) - [What if there is a redirect URL mismatch in local development?](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#what-if-there-is-a-redirect-url-mismatch-in-local-development) - [What does the preflight check do?](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#what-does-the-preflight-check-do) - [Installation](https://plainframework.com/docs/plain-oauth/plain/oauth/?llm#installation) ## Overview This package provides a minimal OAuth integration with no dependencies and a single database model. You can let users sign up and log in with GitHub, Google, Twitter, or any other OAuth provider. Three OAuth flows are supported: 1. **Signup** - new user, new OAuth connection 2. **Login** - existing user, existing OAuth connection 3. **Connect/disconnect** - existing user linking or unlinking an OAuth account Here is a complete example showing GitHub OAuth login. ```python # app/oauth.py import requests from plain.oauth.providers import OAuthProvider, OAuthToken, OAuthUser class GitHubOAuthProvider(OAuthProvider): authorization_url = "https://github.com/login/oauth/authorize" def get_oauth_token(self, *, code, request): response = requests.post( "https://github.com/login/oauth/access_token", headers={"Accept": "application/json"}, data={ "client_id": self.get_client_id(), "client_secret": self.get_client_secret(), "code": code, }, ) response.raise_for_status() data = response.json() return OAuthToken(access_token=data["access_token"]) def get_oauth_user(self, *, oauth_token): response = requests.get( "https://api.github.com/user", headers={ "Accept": "application/json", "Authorization": f"token {oauth_token.access_token}", }, ) response.raise_for_status() data = response.json() return OAuthUser( provider_id=data["id"], user_model_fields={ "email": data["email"], "username": data["login"], }, ) def refresh_oauth_token(self, *, oauth_token): # GitHub tokens don't expire by default return oauth_token ``` Configure the provider in your settings: ```python # app/settings.py OAUTH_LOGIN_PROVIDERS = { "github": { "class": "app.oauth.GitHubOAuthProvider", "kwargs": { "client_id": environ["GITHUB_CLIENT_ID"], "client_secret": environ["GITHUB_CLIENT_SECRET"], # "scope" is optional, defaults to "" }, }, } ``` Add a login button in your template: ```html
``` The provider name in the URL (`'github'`) must match the key in `OAUTH_LOGIN_PROVIDERS`. Your callback URL will be `https://yoursite.com/oauth/github/callback/`. ## Creating a provider You need to subclass [`OAuthProvider`](https://plainframework.com/docs/plain-oauth/plain/oauth/providers.py?llm#OAuthProvider) and implement three methods: - `get_oauth_token` - exchanges an authorization code for an access token - `get_oauth_user` - fetches user information using the access token - `refresh_oauth_token` - refreshes an expired access token (return the same token if the provider does not support refresh) Set the `authorization_url` class attribute to the provider's OAuth authorization endpoint. The [`OAuthToken`](https://plainframework.com/docs/plain-oauth/plain/oauth/providers.py?llm#OAuthToken) class accepts these fields: ```python OAuthToken( access_token="...", refresh_token="", # optional access_token_expires_at=None, # optional datetime refresh_token_expires_at=None, # optional datetime ) ``` The [`OAuthUser`](https://plainframework.com/docs/plain-oauth/plain/oauth/providers.py?llm#OAuthUser) class requires a `provider_id` and an optional dict of fields to set on your User model: ```python OAuthUser( provider_id="12345", # unique ID on the provider's system user_model_fields={ "email": "user@example.com", "username": "example_user", }, ) ``` ## Connecting and disconnecting accounts Authenticated users can connect additional OAuth providers or disconnect existing ones. Add forms to a settings page: ```html