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
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:
- The Angular login page presents a GitHub login button.
- Clicking the button redirects the user to Spring Boot’s OAuth2 authorization endpoint.
- After authentication, Spring Boot generates a JWT and redirects the user back to Angular.
- Angular captures the token and fetches the user profile.
- 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.
<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>
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:
<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:
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
andyour-github-client-secret
with the actual credentials you generated from GitHub. If you’re using environment variables (recommended for security), make sureGITHUB_CLIENT_ID
andGITHUB_CLIENT_SECRET
are defined in your environment. Also make sure the GitHub OAuth2 process is enabled forGITHUB_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.
@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
andproviderId
- 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:
private String provider; // ex. "GITHUB"
private String providerId; // GitHub user ID
5. Updating the Repository
To support provider-based lookups:
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:
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.
getSelf(): Observable<User> {
return this.http.get<User>(`${this.uiConfigService.getApiUrl()}/ninetails/user/self`);
}
Backend:
@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: