OAuth 2.0 Login
How to implement OAuth 2.0 authentication with Google, GitHub, and other providers.
Overview
OAuth 2.0 is the industry standard for delegated authorization. It lets users log in with existing accounts (Google, GitHub, Microsoft) without exposing their passwords to your application. This recipe implements the Authorization Code flow with PKCE in Python, JavaScript, and Java, including state validation and token refresh.
When to Use
Use this resource when:
- You want to offer “Sign in with Google / GitHub” on your platform
- You need to access user data from third-party APIs on their behalf
- You want to reduce password fatigue and improve security
- You’re building a SaaS with enterprise SSO requirements
Solution
Python (Flask + Authlib)
from flask import Flask, redirect, session, url_for
from authlib.integrations.flask_client import OAuth
import secrets
app = Flask(__name__)
app.secret_key = "dev-secret"
oauth = OAuth(app)
google = oauth.register(
name="google",
client_id="GOOGLE_CLIENT_ID",
client_secret="GOOGLE_CLIENT_SECRET",
access_token_url="https://oauth2.googleapis.com/token",
authorize_url="https://accounts.google.com/o/oauth2/auth",
api_base_url="https://www.googleapis.com/oauth2/v1/",
client_kwargs={"scope": "openid email profile"},
)
@app.route("/login")
def login():
redirect_uri = url_for("callback", _external=True)
return google.authorize_redirect(redirect_uri)
@app.route("/callback")
def callback():
token = google.authorize_access_token()
user = google.get("userinfo").json()
session["user"] = user
return redirect("/dashboard")
JavaScript (Express + Passport)
const express = require("express");
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback",
},
(accessToken, refreshToken, profile, done) => {
// Find or create user in DB
return done(null, profile);
}
)
);
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((obj, done) => done(null, obj));
const app = express();
app.use(passport.initialize());
app.use(passport.session());
app.get("/auth/google", passport.authenticate("google", { scope: ["profile", "email"] }));
app.get("/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/login" }),
(req, res) => res.redirect("/dashboard")
);
Java (Spring Security + OAuth2 Client)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.defaultSuccessUrl("/dashboard", true)
)
.logout(logout -> logout
.logoutSuccessUrl("/")
);
return http.build();
}
}
Explanation
The Authorization Code flow works in four steps:
- Redirect: Your app redirects the user to the provider’s authorization URL with
client_id,redirect_uri,scope, and a randomstateparameter. - Consent: The user logs in to the provider and consents to the requested scopes.
- Callback: The provider redirects back to your app with an authorization
code. - Token Exchange: Your backend exchanges the
codefor anaccess_tokenandid_tokenusing yourclient_secret.
PKCE (Proof Key for Code Exchange) adds a secret verifier to prevent interception attacks on mobile and SPA apps. State prevents CSRF by binding the callback to the original request.
Variants
| Flow | Use Case | Client Secret? | PKCE? |
|---|---|---|---|
| Authorization Code | Server-side web apps | Yes | Optional |
| Authorization Code + PKCE | SPAs, mobile apps | No | Required |
| Implicit (deprecated) | Legacy SPAs | No | No |
| Client Credentials | Machine-to-machine | Yes | No |
| Device Code | TVs, CLI tools | No | No |
Best Practices
- Use PKCE even for server apps: It’s a one-line addition and eliminates code interception risk.
- Validate the
stateparameter: Always compare the state in the callback with the one stored in the user’s session. - Store tokens encrypted: Access tokens and refresh tokens are as sensitive as passwords.
- Implement token refresh: Access tokens expire quickly; use refresh tokens to maintain sessions.
- Scope minimally: Only request permissions your app actually needs.
Common Mistakes
- Skipping state validation: Opens your app to CSRF login attacks.
- Storing tokens in localStorage: XSS can steal them. Use httpOnly cookies.
- Not handling token revocation: Users expect “Log out everywhere” to work.
- Hardcoding redirect URIs: Must match the provider’s registered URIs exactly.
- Ignoring consent screen branding: A generic OAuth consent screen reduces conversion rates.
Frequently Asked Questions
Can I use OAuth 2.0 for machine-to-machine authentication?
Yes, with the Client Credentials flow. The client authenticates directly with its ID and secret (or client assertion JWT) to obtain an access token. No user interaction is involved. This is ideal for backend services, cron jobs, and microservices.
How do I support multiple providers (Google, GitHub, Microsoft)?
Use a library that abstracts provider differences (Passport.js, Authlib, Spring Security). Store provider-specific fields (provider, provider_user_id) in your user table. Normalize email/name fields across providers to create a unified user profile.
What is the difference between OAuth 2.0 and OpenID Connect?
OAuth 2.0 is an authorization framework (“Can this app access my data?”). OpenID Connect (OIDC) is an authentication layer built on top of OAuth 2.0 that standardizes identity claims (id_token, /userinfo). If you only need login (who is this user?), OIDC is sufficient. If you need API access, you need OAuth 2.0 scopes.
Related Resources
JWT Authentication
How to generate, validate, and refresh JSON Web Tokens for stateless API authentication.
RecipePassword Hashing
How to securely hash and verify passwords using modern algorithms across Python, JavaScript, and Java.
RecipeMiddleware
How to implement request/response middleware for logging, auth, and error handling across Python, JavaScript, and Java.
PatternAbstract Factory Pattern
Create families of related objects without specifying concrete classes. A creational design pattern for consistent object families.
PatternAdapter Pattern
Convert the interface of a class into another interface clients expect. A structural design pattern for interface compatibility.