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
Managing passwords is hard:
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 is an authorization framework. It lets you delegate authentication to a third party.
Flow:
https://accounts.google.com/o/oauth2/v2/auth?client_id=xxx&redirect_uri=https://myapp.com/callback&scope=openid%20email&response_type=codehttps://myapp.com/callback?code=abc123&state=xyz789POST 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
{"access_token": "ya29...", "id_token": "eyJhbGc...", "expires_in": 3600}Key points:
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.
Authorization Code Flow (recommended):
Benefit: the access token never goes through the frontend; only the backend sees it.
Implicit Flow (deprecated):
Downside: the access token is visible in the browser, creating XSS/CSP risks.
Always use the authorization code flow.
https://myapp.comhttps://myapp.com/callback<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>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 tokenIn a multi-tenant app, you might want to force users to log in with a company email:
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.
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.
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?
Auth, Authorization & Multi-Tenancy