OAuth2 Integration Guide - jnleyva816/NextMove GitHub Wiki
🔐 Comprehensive guide to OAuth2 authentication implementation in NextMove
This guide covers the complete OAuth2 integration in NextMove, including Google and Microsoft providers, user authentication flows, and security configurations.
- Overview
- Supported Providers
- Architecture
- Backend Implementation
- Frontend Integration
- Security Configuration
- Interview Reminder System
- Error Handling
- Testing
- Troubleshooting
NextMove implements OAuth2 authentication to provide secure, seamless user authentication using popular identity providers. The system supports both traditional email/password authentication and OAuth2 social login.
- ✅ Multiple Providers: Google and Microsoft OAuth2 integration
- ✅ JWT Authentication: Secure token-based authentication
- ✅ Account Linking: Link OAuth2 accounts to existing user profiles
- ✅ Interview Reminders: Automated email notifications for scheduled interviews
- ✅ Secure Flows: Comprehensive error handling and security measures
- ✅ User Experience: Smooth authentication flows with proper redirects
- Provider: Google Identity Platform
-
Scopes:
openid
,profile
,email
-
Redirect URI:
{BASE_URL}/oauth2/callback/google
- User Info: Full name, email, profile picture
- Provider: Microsoft Azure AD / Microsoft Account
-
Scopes:
openid
,profile
,email
-
Redirect URI:
{BASE_URL}/oauth2/callback/microsoft
- User Info: Full name, email, profile picture
sequenceDiagram
participant User
participant Frontend
participant Backend
participant OAuth2Provider
participant Database
User->>Frontend: Click "Login with Google/Microsoft"
Frontend->>Backend: Initiate OAuth2 flow
Backend->>OAuth2Provider: Redirect to authorization URL
OAuth2Provider->>User: Present login screen
User->>OAuth2Provider: Provide credentials
OAuth2Provider->>Backend: Authorization code callback
Backend->>OAuth2Provider: Exchange code for tokens
OAuth2Provider->>Backend: Return access token & user info
Backend->>Database: Create/update user record
Backend->>Frontend: Return JWT token
Frontend->>User: Successful authentication
- OAuth2 Controllers: Handle authentication flows and callbacks
- Custom User Services: Process OAuth2 user information
- JWT Filter: Validate and process authentication tokens
- Security Configuration: Configure OAuth2 settings and security rules
- Frontend Components: OAuth2 success/error pages and authentication flows
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.authorizationEndpoint(authorization -> authorization
.baseUri("/oauth2/authorize")
)
.redirectionEndpoint(redirection -> redirection
.baseUri("/oauth2/callback/*")
)
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
.successHandler(oAuth2AuthenticationSuccessHandler)
.failureHandler(oAuth2AuthenticationFailureHandler)
);
return http.build();
}
}
The custom user service processes OAuth2 user information and creates/updates user records:
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oauth2User = super.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId();
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
OAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(registrationId, oauth2User.getAttributes());
return processOAuth2User(userRequest, userInfo);
}
}
After successful OAuth2 authentication, the system generates JWT tokens:
@Component
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
String token = tokenProvider.createToken(authentication);
String targetUrl = determineTargetUrl(request, response, authentication);
if (response.isCommitted()) {
return;
}
clearAuthenticationAttributes(request, response);
getRedirectStrategy().sendRedirect(request, response, targetUrl + "?token=" + token);
}
}
The frontend initiates OAuth2 authentication by redirecting to the backend endpoints:
// OAuth2 login initiation
const handleGoogleLogin = () => {
window.location.href = `${API_BASE_URL}/oauth2/authorize/google?redirect_uri=${FRONTEND_URL}/oauth2/redirect`;
};
const handleMicrosoftLogin = () => {
window.location.href = `${API_BASE_URL}/oauth2/authorize/microsoft?redirect_uri=${FRONTEND_URL}/oauth2/redirect`;
};
Handles successful authentication and token processing:
import React, { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
const OAuth2Success: React.FC = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
useEffect(() => {
const token = searchParams.get('token');
const error = searchParams.get('error');
if (token) {
localStorage.setItem('authToken', token);
navigate('/dashboard');
} else if (error) {
navigate('/oauth2/error');
}
}, [searchParams, navigate]);
return <div>Processing authentication...</div>;
};
Comprehensive error page for authentication failures:
const OAuth2Error: React.FC = () => {
const [searchParams] = useSearchParams();
const error = searchParams.get('error');
const getErrorMessage = (error: string | null) => {
switch (error) {
case 'invalid_client':
return 'Invalid OAuth2 client configuration';
case 'access_denied':
return 'Authentication was cancelled or denied';
case 'server_error':
return 'An unexpected server error occurred';
default:
return 'An authentication error occurred';
}
};
return (
<div className="oauth2-error">
<h2>Authentication Failed</h2>
<p>{getErrorMessage(error)}</p>
<button onClick={() => navigate('/login')}>
Try Again
</button>
</div>
);
};
- Token Expiration: 24 hours for access tokens
- Secure Storage: Tokens stored securely in localStorage
- Token Validation: Server-side validation on every request
- Refresh Mechanism: Automatic token refresh when needed
- CSRF Protection: Built-in CSRF protection for OAuth2 flows
- State Parameter: Random state parameter to prevent CSRF attacks
- Secure Redirects: Whitelist of allowed redirect URIs
- HTTPS Only: OAuth2 flows only work over HTTPS in production
# OAuth2 Google Configuration
spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID}
spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
spring.security.oauth2.client.registration.google.scope=openid,profile,email
# OAuth2 Microsoft Configuration
spring.security.oauth2.client.registration.microsoft.client-id=${MICROSOFT_CLIENT_ID}
spring.security.oauth2.client.registration.microsoft.client-secret=${MICROSOFT_CLIENT_SECRET}
spring.security.oauth2.client.registration.microsoft.scope=openid,profile,email
# JWT Configuration
app.jwtSecret=${JWT_SECRET}
app.jwtExpirationInMs=86400000
The OAuth2 integration includes an automated interview reminder system:
@Service
public class InterviewReminderService {
@Scheduled(fixedRate = 3600000) // Run every hour
public void sendInterviewReminders() {
List<Application> upcomingInterviews = applicationRepository
.findUpcomingInterviews(LocalDateTime.now().plusHours(24));
for (Application application : upcomingInterviews) {
if (!application.isReminderSent()) {
emailService.sendInterviewReminder(application);
application.setReminderSent(true);
applicationRepository.save(application);
}
}
}
}
Professional email templates for user notifications:
- Account Linking: Welcome emails for OAuth2 account linking
- Interview Reminders: Automated reminders with interview details
- Security Notifications: Account security and login notifications
- Invalid Client: Incorrect OAuth2 client configuration
- Access Denied: User cancelled authentication
- Invalid Grant: Authorization code expired or invalid
- Server Error: Unexpected server errors during authentication
@Component
public class OAuth2AuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException {
String targetUrl = determineTargetUrl(request, response, exception);
if (response.isCommitted()) {
logger.debug("Response has already been committed. Unable to redirect to " + targetUrl);
return;
}
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
private String determineTargetUrl(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) {
String targetUrl = getDefaultFailureUrl();
if (exception instanceof OAuth2AuthenticationException) {
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
targetUrl += "?error=" + error.getErrorCode();
}
return targetUrl;
}
}
Comprehensive test coverage for OAuth2 components:
@ExtendWith(MockitoExtension.class)
class CustomOAuth2UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private CustomOAuth2UserService customOAuth2UserService;
@Test
void testLoadUser_NewGoogleUser() {
// Test OAuth2 user creation flow
OAuth2UserRequest userRequest = createMockUserRequest("google");
OAuth2User result = customOAuth2UserService.loadUser(userRequest);
assertNotNull(result);
verify(userRepository).save(any(User.class));
}
@Test
void testLoadUser_ExistingMicrosoftUser() {
// Test existing user OAuth2 flow
OAuth2UserRequest userRequest = createMockUserRequest("microsoft");
OAuth2User result = customOAuth2UserService.loadUser(userRequest);
assertNotNull(result);
verify(userRepository).findByEmail(anyString());
}
}
End-to-end OAuth2 flow testing:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class OAuth2IntegrationTest {
@Test
void testOAuth2AuthenticationFlow() {
// Test complete OAuth2 authentication flow
// Including token generation and user creation
}
}
Problem: Invalid client ID or secret Solution: Verify environment variables and OAuth2 provider configuration
Problem: Redirect URI not matching configured values Solution: Ensure frontend and backend redirect URIs match OAuth2 provider settings
Problem: JWT token validation errors Solution: Check JWT secret configuration and token expiration settings
Problem: Cross-origin requests blocked Solution: Configure CORS settings for OAuth2 endpoints
Enable OAuth2 debug logging:
logging.level.org.springframework.security.oauth2=DEBUG
logging.level.org.springframework.security.web.authentication=DEBUG
logging.level.com.jnleyva.nextmove_backend.security=DEBUG
Monitor OAuth2 integration health:
@Component
public class OAuth2HealthIndicator implements HealthIndicator {
@Override
public Health health() {
// Check OAuth2 provider connectivity
// Validate JWT configuration
// Return health status
return Health.up()
.withDetail("google", "UP")
.withDetail("microsoft", "UP")
.withDetail("jwt", "CONFIGURED")
.build();
}
}
- Additional Providers: GitHub, LinkedIn OAuth2 integration
- Refresh Tokens: Implement refresh token mechanism
- Multi-Factor Authentication: Add MFA support for OAuth2 users
- Session Management: Advanced session management and monitoring
- Audit Logging: Comprehensive authentication event logging
- Rate Limiting: OAuth2 endpoint rate limiting
- IP Whitelisting: Restrict OAuth2 access by IP
- Advanced Monitoring: Real-time security monitoring
- Token Revocation: Implement token revocation endpoints
🔐 Security Note: Always keep OAuth2 client secrets secure and rotate them regularly. Never commit secrets to version control.