Careeroza
Real World Example
Where You See This
- Swiggy, Zomato, Myntra: "Continue with Google" on the web and Android apps (authorization code + PKCE).
- Paytm and PhonePe "Login with Paytm" on partner sites: OAuth2 consent screens with scopes like "basic profile".
- DigiLocker: government partner apps use OAuth2 to fetch your documents with your consent.
- GitHub apps, Slack apps: third-party apps get scoped tokens instead of your password.
ShopEase: Google Login for a React Frontend
ShopEase uses a Backend-for-Frontend (BFF): the Spring Boot app is the OAuth2 client and keeps tokens on the server. The React app only holds an HttpOnly session cookie, so no token can be stolen by XSS. After login, the user is sent back to the React app:
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http,
ShopEaseOidcUserService oidcUserService,
@Value("${shopease.frontend-url}") String frontendUrl) throws Exception {
CookieCsrfTokenRepository csrfRepo = CookieCsrfTokenRepository.withHttpOnlyFalse(); // React reads XSRF-TOKEN
http
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/products/**", "/api/categories/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll())
.oauth2Login(oauth -> oauth
.userInfoEndpoint(ui -> ui.oidcUserService(oidcUserService))
.successHandler((request, response, authentication) ->
response.sendRedirect(frontendUrl + "/account"))
.failureHandler((request, response, exception) ->
response.sendRedirect(frontendUrl + "/login?error=" + URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8))))
.exceptionHandling(ex -> ex.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), PathPatternRequestMatcher.withDefaults().matcher("/api/**")))
.csrf(csrf -> csrf.csrfTokenRepository(csrfRepo)
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()))
.logout(logout -> logout.logoutUrl("/api/logout")
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.NO_CONTENT)));
return http.build();
}// React: the login button is a plain link to the backend
export function LoginButtons() {
return (
<div className="login-buttons">
<a className="btn" href="/oauth2/authorization/google">Continue with Google</a>
</div>
);
}
// Any API call sends the session cookie automatically (same site via the dev proxy / same domain)
const res = await fetch('/api/me', { credentials: 'include' });
if (res.status === 401) navigate('/login');Log output
Server log on a first-time Google login:
tail -f logs/shopease.logExample output
2026-03-01T10:02:11.482+05:30 INFO 18244 --- [shopease] [nio-8080-exec-4] i.s.security.ShopEaseOidcUserService : New customer created from google: [email protected] (sub=110248495921238986420) 2026-03-01T10:02:11.497+05:30 INFO 18244 --- [shopease] [nio-8080-exec-4] i.s.security.LoginAuditListener : LOGIN_SUCCESS [email protected] method=oauth2:google ip=49.36.112.18
Note: PathPatternRequestMatcher is the Spring Security 6.5+ matcher; on older 6.x versions use new AntPathRequestMatcher("/api/**").