OAuth2 and OpenID Connect
OAuth2 lets users log in using a third-party identity provider (Google, GitHub). You redirect to Google, they authenticate, Google sends you back a token. OpenID Connect adds authentication on top of OAuth2. Delegating authentication to a trusted provider reduces your responsibility for password security.
5 min read
The problem: your own authentication is risky
Managing passwords is hard:
- You must hash them correctly (bcrypt, not SHA-256).
- You must handle password resets securely.
- You must prevent brute-force attacks.
- If you're breached, millions of passwords are exposed.
Delegation: let Google, GitHub, or another trusted provider handle authentication. You redirect users to Google, they log in there, and Google tells you "yes, this is alice@example.com."
This is OAuth2 / OpenID Connect.
OAuth2 at a glance
OAuth2 is an authorization framework. It lets you delegate authentication to a third party.
Flow:
- Alice clicks "Sign in with Google" on your website.
- You redirect Alice to Google:
https://accounts.google.com/o/oauth2/v2/auth?client_id=xxx&redirect_uri=https://myapp.com/callback&scope=openid%20email&response_type=code - Google shows a login form: Alice enters her email/password.
- Alice grants permission: "Let myapp.com access your email and profile."
- Google redirects back to you:
https://myapp.com/callback?code=abc123&state=xyz789 - You exchange the code for a token: backend-to-backend call to Google's token endpoint:
POST https://oauth2.googleapis.com/token client_id=xxx client_secret=yyy (your app's secret, kept on the backend) code=abc123 redirect_uri=https://myapp.com/callback - Google sends back an access token:
{"access_token": "ya29...", "id_token": "eyJhbGc...", "expires_in": 3600} - You verify the token: check the signature, extract alice@example.com, and create an account or session for alice.
- Alice is logged in on your app.
Key points:
- Alice's password never touches your servers.
- Google handles password security.
- You only deal with tokens.
OAuth2 terms
- Authorization Server (Google): the trusted provider.
- Resource Owner (Alice): the user.
- Client (your website): the app asking for access.
- Authorization Code (abc123): a short-lived, single-use code proving Alice approved your app.
- Access Token (ya29...): a token proving your app can access Alice's data on Google's servers.
- Redirect URI (https://myapp.com/callback): where Google sends Alice back after she approves.
- Client ID and Client Secret: your app's credentials with Google.
OpenID Connect (OIDC): OAuth2 + authentication
OAuth2 is designed for authorization (accessing resources). OpenID Connect (OIDC) adds a layer for authentication (proving identity).
The difference:
OAuth2 access token: "your app can read alice's Google Calendar."
OIDC id_token: "alice@example.com is authenticated, and Google verified her."
An id_token is a JWT signed by Google. When Google sends it back, you can verify the signature and trust the claims inside:
{
"iss": "https://accounts.google.com",
"sub": "110169547959700850118", // Google's unique ID for Alice
"email": "alice@example.com",
"email_verified": true,
"aud": "YOUR_CLIENT_ID",
"iat": 1692921600,
"exp": 1692925200
}Most "social login" integrations use OIDC because the id_token tells you exactly who the user is.
OAuth2 code vs implicit flow
Authorization Code Flow (recommended):
- Frontend redirects to Google.
- Google sends a code back to the frontend.
- Frontend sends the code to your backend.
- Backend exchanges code for a token (backend-to-backend call).
Benefit: the access token never goes through the frontend; only the backend sees it.
Implicit Flow (deprecated):
- Frontend redirects to Google.
- Google sends the access token directly to the frontend.
Downside: the access token is visible in the browser, creating XSS/CSP risks.
Always use the authorization code flow.
Setting up OAuth2: Google example
- Create a Google Cloud project: go to console.cloud.google.com.
- Create OAuth2 credentials:
- Authorized JavaScript origins:
https://myapp.com - Authorized redirect URIs:
https://myapp.com/callback - Copy the Client ID and Client Secret.
- Authorized JavaScript origins:
- Frontend: add a "Sign in with Google" button:
<button onclick="loginWithGoogle()">Sign in with Google</button> <script src="https://accounts.google.com/gsi/client" async defer></script> <script> function loginWithGoogle() { google.accounts.id.initialize({ client_id: 'xxx' }); google.accounts.id.renderButton( document.getElementById('buttonDiv'), { theme: 'outline', size: 'large' } ); } </script> - Backend: handle the callback:
from google.auth.transport import requests from google.oauth2 import id_token @app.post('/callback') def handle_callback(): token = request.json.get('credential') # JWT from Google try: id_info = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID) email = id_info.get('email') # Create or find user user = db.find_or_create_user(email=email) issue_session_token(user) except ValueError: return 401 # invalid token
Multi-tenant OAuth2
In a multi-tenant app, you might want to force users to log in with a company email:
- Alice (alice@company-a.com) can only log in with her company Google Workspace account.
- Bob (bob@company-b.com) can only log in with his company's Google Workspace account.
Configure a hd (hosted domain) parameter:
https://accounts.google.com/o/oauth2/v2/auth?...&hd=company-a.com
This restricts login to company-a.com accounts only. If Bob tries to log in with company-b.com, Google will reject it.
Common mistakes
Storing the access token as a session credential
# RISKY
request.session['access_token'] = access_token_from_googleThe access token is for accessing Google's APIs, not your app. If it leaks, the attacker can access Google data. Use a separate session/JWT for your app.
Not verifying the id_token signature
# WRONG — trusts the token without verification
payload = jwt.decode(token, options={"verify_signature": False})
# CORRECT — verifies the signature against Google's public keys
id_info = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)Not checking the state parameter
# WRONG — ignores state
@app.get('/callback')
def callback():
code = request.args.get('code')
# exchange code for tokenCORRECT
# Frontend: store a random state before redirecting to Google
state = generate_random_token()
request.session['oauth_state'] = state
# Redirect: ...&state=<state>
# Backend: verify state matches
@app.get('/callback')
def callback():
state = request.args.get('state')
if state != request.session.get('oauth_state'):
return 403 # CSRF attack
code = request.args.get('code')
# exchange code for tokenThe state parameter prevents CSRF attacks. An attacker can't forge a valid callback without knowing the random state.
Blindly trusting the email in the id_token
# RISKY
email = request.json.get('email') # from frontend
user = db.find_or_create_user(email=email)The frontend sends whatever it wants. Always verify the JWT signature first; only then trust the claims inside.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What is the main purpose of OAuth2?
2. In OAuth2, what is the authorization code used for?
3. What is the difference between OAuth2 and OpenID Connect?
4. What does the state parameter in OAuth2 prevent?