GitHub OAuth2 App Settings

How I Implemented GitHub OAuth2 into My Spring Boot + Angular App N1netails

Not everyone wants to log into a website using a basic email and password form. Sometimes it’s due to a lack of trust, and other times, just pure convenience. That’s why many websites now offer OAuth2 login options—reducing the cognitive load of remembering passwords and adding an extra layer of trust via a known provider.

I recently implemented GitHub OAuth2 login in my N1netails application and wanted to share how I got it working.

N1netails is built using the JASP stack (Java, Angular, Spring Boot, and PostgreSQL). Okay, I made up the acronym, but you get the idea. Spring Boot greatly simplifies OAuth2 integration—it handles most of the “magic” behind the scenes. But integrating it smoothly with an Angular frontend still takes some work. I initially followed Spring Boot and OAuth2, but found it lacking guidance for an Angular setup. So here’s the full walkthrough based on what I learned.

You can also check out the actual pull request here: GitHub OAuth2 Implementation Pull Request

Setting Up a GitHub OAuth2 App

Before getting started, you’ll need to create a GitHub OAuth2 App via Developer Settings.

Here’s what I used for the configuration:

  • Application name: n1netails-local
  • Homepage URL: https://localhost:4200
  • Application description: (Add a brief description of your app)
  • Authorization callback URL: http://localhost:9901/login/oauth2/code/github

GitHub OAuth2 App Settings

Once you’ve completed the setup, GitHub will generate a Client ID and Client Secret. Make sure to save these values—they’ll be added to your application.properties (or application.yml) later for Spring Boot to use.

The OAuth2 Flow (Spring Boot + Angular)

Here’s the basic flow I implemented:

  1. The Angular login page presents a GitHub login button.
  2. Clicking the button redirects the user to Spring Boot’s OAuth2 authorization endpoint.
  3. After authentication, Spring Boot generates a JWT and redirects the user back to Angular.
  4. Angular captures the token and fetches the user profile.
  5. The app is now authenticated and fully functional for the user.

1. GitHub Login Button in Angular

On the login page, I created a GitHub login button. When clicked, it redirects the user to the backend for GitHub authorization.

login.component.html

<button 
  *ngIf="githubAuthEnabled"
  nz-button
  nzType="default"
  (click)="loginWithGithub()"
  class="login-btn github-login-btn"
  type="button"
>
 <nz-icon nzType="github" nzTheme="outline" style="margin-right: 8px;"/>
 Login with GitHub
</button>

login.component.ts

loginWithGithub() {
  // Redirects to Spring Boot's OAuth2 authorization endpoint
  // window.location.href = 'http://localhost:9901/oauth2/authorization/github';
  window.location.href = this.uiConfigService.getApiUrl() + '/oauth2/authorization/github';
}

2. Backend: Spring Boot OAuth2 Configuration

Start by adding the required Spring Security OAuth2 dependency to your backend:

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

This automatically enables the /oauth2/authorization/github endpoint, which Spring Security uses to initiate the OAuth2 login flow.

Next, configure your OAuth2 settings in application.properties or application.yml. Here’s what my setup looks like using YAML:

application-oauth.yml

auth:
  github:
    enabled: ${GITHUB_OAUTH2_ENABLED:true}
  oauth2:
    redirects:
      success: ${AUTH_OAUTH2_REDIRECT_SUCCESS:http://localhost:4200/#/oauth2/success?token=}

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: ${GITHUB_CLIENT_ID:your-github-client-id}
            client-secret: ${GITHUB_CLIENT_SECRET:your-github-client-secret}
            scope: read:user,user:email
            redirect-uri: "{baseUrl}/login/oauth2/code/github"
            client-name: GitHub
        provider:
          github:
            authorization-uri: https://github.com/login/oauth/authorize
            token-uri: https://github.com/login/oauth/access_token
            user-info-uri: https://api.github.com/user

🔐 Note: Replace your-github-client-id and your-github-client-secret with the actual credentials you generated from GitHub. If you’re using environment variables (recommended for security), make sure GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET are defined in your environment. Also make sure the GitHub OAuth2 process is enabled for GITHUB_OAUTH2_ENABLED.

This configuration tells Spring Security how to interact with GitHub’s OAuth2 endpoints and how to handle the redirect after successful authentication.

Since my app already supports email/password login via JWT, I created a separate Spring Security config for OAuth2. This allows GitHub auth to be handled with higher priority by using @Order.

Oauth2LoginSecurityConfig.java

@Order(1)
@Configuration
@ConditionalOnProperty(prefix = "auth.github", name = "enabled", havingValue = "true")
public class Oauth2LoginSecurityConfig {
    http
        .securityMatcher("/oauth2/**", "/login/**")
        .authorizeHttpRequests(auth -> auth
                .requestMatchers("/oauth2/**", "/login/**").authenticated()
                .anyRequest().permitAll()
        )
        .csrf(AbstractHttpConfigurer::disable)
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
        .oauth2Login(oauth2 -> oauth2
                .successHandler((request, response, authentication) -> {
                    OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
                    OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();

                    String jwtToken = oAuth2Service.loginGithub(oAuth2User, oauthToken);
                    // Redirect to Angular app with the token as query param (ex. "http://localhost:4200/oauth2/success?token=" + jwtToken)
                    response.sendRedirect(oAuth2RedirectsSuccess + jwtToken);
                })
        );

    return http.build();
}

Note: I used SessionCreationPolicy.IF_REQUIRED to allow Spring Security to maintain session state only when needed, without affecting my JWT-based setup.

3. Handling the OAuth2 Login in Backend

This is where most of the GitHub integration logic lives—inside the OAuth2Service. It handles user retrieval, creation, linking accounts, and generating JWTs.

OAuth2ServiceImpl.java

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
@Qualifier("oAuth2Service")
public class OAuth2ServiceImpl implements OAuth2Service {

    private final UserRepository userRepository;
    private final OrganizationRepository organizationRepository;
    private final JwtTokenUtil jwtTokenUtil;

    private final OAuth2AuthorizedClientService authorizedClientService;
    private final RestTemplate restTemplate = new RestTemplate();

    @Override
    public String loginGithub(OAuth2User oAuth2User, OAuth2AuthenticationToken authentication) {

        log.info("loginGithub");
        log.info("getting user info from OAuth2User");
        String provider = "GITHUB";
        log.info("providerId");
        String providerId = Optional.ofNullable(oAuth2User.getAttribute("id")).map(Object::toString).orElse(null);
        log.info("username");
        String username = oAuth2User.getAttribute("login");
        log.info("email");
        String email = oAuth2User.getAttribute("email"); // may be null
        log.info("avatarUrl");
        String avatarUrl = oAuth2User.getAttribute("avatar_url");
        log.info("name");
        String name = oAuth2User.getAttribute("name");
        log.info("getting user info from OAuth2User COMPLETED");

        OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(
                authentication.getAuthorizedClientRegistrationId(),
                authentication.getName()
        );
        String accessToken = client.getAccessToken().getTokenValue();

        if (email == null) email = fetchPrimaryEmailFromGithub(accessToken);
        if (email == null) email = username + "@users.noreply.github.com";

        // Step 1: Try to find existing user by provider+providerId
        UsersEntity user = userRepository.findByProviderAndProviderId(provider, providerId).orElse(null);

        if (user == null) {
            // Step 2: Try to find by email
            user = userRepository.findUserByEmail(email).orElse(null);

            if (user != null) {
                // Step 3: Link GitHub to existing user
                log.info("Linking GitHub to existing user: {}", email);
                user.setProvider(provider);
                user.setProviderId(providerId);
            } else {
                // Step 4: New user registration
                log.info("Creating new user from GitHub OAuth2");
                user = new UsersEntity();
                user.setProvider(provider);
                user.setProviderId(providerId);
                user.setUserId(UserUtil.generateUserId());
                user.setJoinDate(new Date());
                user.setActive(true);
                user.setEnabled(true);
                user.setNotLocked(true);
                user.setRole(com.n1netails.n1netails.api.model.enumeration.Role.ROLE_USER.name());
                user.setAuthorities(Authority.USER_AUTHORITIES);

                OrganizationEntity n1netailsOrg = organizationRepository.findByName("n1netails")
                        .orElseThrow(() -> new RuntimeException("Default 'n1netails' organization not found."));

                user.setOrganizations(new HashSet<>(Set.of(n1netailsOrg)));
            }
        }

        // Common fields for all paths
        user.setEmail(email);
        user.setUsername(username);
        user.setProfileImageUrl(avatarUrl);

        // Set names
        if (name != null && !name.isBlank()) {
            int lastSpace = name.lastIndexOf(" ");
            if (lastSpace != -1) {
                user.setFirstName(name.substring(0, lastSpace));
                user.setLastName(name.substring(lastSpace + 1));
            } else {
                user.setFirstName(name);
            }
        }

        user.setLastLoginDateDisplay(user.getLastLoginDate());
        user.setLastLoginDate(new Date());

        log.info("Saving user from GitHub OAuth2");
        userRepository.save(user);

        return jwtTokenUtil.createToken(new UserPrincipal(user));
    }


    private String fetchPrimaryEmailFromGithub(String accessToken) {
        String url = "https://api.github.com/user/emails";
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "token " + accessToken);

        HttpEntity<Void> entity = new HttpEntity<>(headers);
        ResponseEntity<List<Map<String, Object>>> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                new ParameterizedTypeReference<>() {}
        );

        if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
            for (Map<String, Object> emailObj : response.getBody()) {
                Boolean primary = (Boolean) emailObj.get("primary");
                Boolean verified = (Boolean) emailObj.get("verified");
                String email = (String) emailObj.get("email");
                if (primary != null && primary && verified != null && verified) {
                    return email;
                }
            }
        }
        return null; // fallback if no primary email found
    }
}

Key highlights:

  • Tries to find a user by provider and providerId
  • If not found, checks by email to potentially link accounts
  • If still not found, creates a new user
  • Finally, returns a JWT for the session

If GitHub doesn’t provide the email, we fetch it manually using the access token:

private String fetchPrimaryEmailFromGithub(String accessToken) {
    ...
    return email; // if primary + verified found
}

4. Updating the User Entity

I added provider and providerId fields to track OAuth2 providers like GitHub:

UsersEntity.java

private String provider;    // ex. "GITHUB"
private String providerId;  // GitHub user ID

5. Updating the Repository

To support provider-based lookups:

UserRepository.java

Optional<UsersEntity> findByProviderAndProviderId(String provider, String providerId);

6. Redirecting Back to Angular After Successful OAuth2 Login

After successful login, Spring redirects the user to Angular with the JWT token:

Oauth2LoginSecurityConfig.java

response.sendRedirect(oAuth2RedirectsSuccess + jwtToken);

7. Angular: Capturing the JWT Token

In Angular, I created a route /oauth2/success and a component to process the token:

oauth2-success.component.ts

ngOnInit(): void {
  this.route.queryParams.subscribe(params => {
    const token = params['token'];
    if (token) {
      this.authenticationService.saveToken(token);
      this.userService.getSelf().subscribe(user => {
        this.authenticationService.addUserToLocalCache(user);
        this.router.navigate(['/dashboard']);
      });
    } else {
      this.router.navigate(['/login']);
    }
  });
}

8. Getting the Authenticated User

The frontend calls the /self endpoint to fetch the user’s profile using the JWT token.

user.service.ts

getSelf(): Observable<User> {
  return this.http.get<User>(`${this.uiConfigService.getApiUrl()}/ninetails/user/self`);
}

Backend:

UserController.java

@GetMapping("/self")
public ResponseEntity<UsersEntity> getCurrentUser(@RequestHeader(AUTHORIZATION) String authorizationHeader) {
    ...
    return ResponseEntity.ok(user);
}

Wrap-Up and Improvements

That’s the full OAuth2 login integration with GitHub in my Spring Boot + Angular app.

A few potential improvements I’m considering:

  • Adding a failure handler inside Oauth2LoginSecurityConfig
  • Simplifying the logic in the /self endpoint
  • Adding support for other providers like Google or LinkedIn

You can check out the full PR again here: GitHub OAuth2 Implementation Pull Request

If you’re curious about Project N1netails, here are some links:

Similar Posts