OAuth2 Integration Guide - jnleyva816/NextMove GitHub Wiki

OAuth2 Integration Guide

🔐 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.


Table of Contents

  1. Overview
  2. Supported Providers
  3. Architecture
  4. Backend Implementation
  5. Frontend Integration
  6. Security Configuration
  7. Interview Reminder System
  8. Error Handling
  9. Testing
  10. Troubleshooting

Overview

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.

Key Features

  • 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

Supported Providers

Google OAuth2

  • Provider: Google Identity Platform
  • Scopes: openid, profile, email
  • Redirect URI: {BASE_URL}/oauth2/callback/google
  • User Info: Full name, email, profile picture

Microsoft OAuth2

  • Provider: Microsoft Azure AD / Microsoft Account
  • Scopes: openid, profile, email
  • Redirect URI: {BASE_URL}/oauth2/callback/microsoft
  • User Info: Full name, email, profile picture

Architecture

Authentication Flow

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
Loading

System Components

  1. OAuth2 Controllers: Handle authentication flows and callbacks
  2. Custom User Services: Process OAuth2 user information
  3. JWT Filter: Validate and process authentication tokens
  4. Security Configuration: Configure OAuth2 settings and security rules
  5. Frontend Components: OAuth2 success/error pages and authentication flows

Backend Implementation

OAuth2 Configuration

@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();
    }
}

Custom OAuth2 User Service

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);
    }
}

JWT Token Generation

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);
    }
}

Frontend Integration

OAuth2 Authentication Flow

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`;
};

OAuth2 Success Page

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>;
};

OAuth2 Error Handling

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>
  );
};

Security Configuration

JWT Security

  • 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

OAuth2 Security

  • 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

Environment Configuration

# 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

Interview Reminder System

Email Notification Service

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);
            }
        }
    }
}

Email Templates

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

Error Handling

Common OAuth2 Errors

  1. Invalid Client: Incorrect OAuth2 client configuration
  2. Access Denied: User cancelled authentication
  3. Invalid Grant: Authorization code expired or invalid
  4. Server Error: Unexpected server errors during authentication

Error Resolution

@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;
    }
}

Testing

Unit Tests

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());
    }
}

Integration Tests

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
    }
}

Troubleshooting

Common Issues

1. OAuth2 Client Configuration

Problem: Invalid client ID or secret Solution: Verify environment variables and OAuth2 provider configuration

2. Redirect URI Mismatch

Problem: Redirect URI not matching configured values Solution: Ensure frontend and backend redirect URIs match OAuth2 provider settings

3. Token Validation Failures

Problem: JWT token validation errors Solution: Check JWT secret configuration and token expiration settings

4. CORS Issues

Problem: Cross-origin requests blocked Solution: Configure CORS settings for OAuth2 endpoints

Debug Configuration

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

Health Checking

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();
    }
}

Next Steps

Planned Enhancements

  • 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

Security Improvements

  • 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.

⚠️ **GitHub.com Fallback** ⚠️