Implementing OAuth2 login with Spring Security and GitHub

Spring Security can implement OAuth2 login with GitHub, giving users a familiar sign-in without storing their GitHub password. It handles the browser redirect, authorisation code exchange and authenticated session, while the application receives an approved user profile.

For Australian teams, this can be a practical fit. Sydney-based financial and enterprise customers often expect auditable access, while Melbourne startups may want GitHub onboarding with little administration.

The flow still needs sound application design: stable identifiers, server-controlled roles, protected secrets and tests for account changes. This example follows GitHub’s authorisation code flow and fits a Spring Boot service with a JPA-compatible local identity model.

Registering the GitHub OAuth application

Create an OAuth app in GitHub’s developer settings and register the callback URL Spring will call. For a local Boot application on port 8080, use http://localhost:8080/login/oauth2/code/github. A production deployment needs its own HTTPS callback, such as https://app.example.com.au/login/oauth2/code/github. GitHub matches callback URIs exactly, including the scheme, port and path.

Copy the generated client ID and client secret into a password manager or deployment secret store. Request read:user for profile details, and add user:email only if the application also retrieves GitHub’s verified email endpoint. Keep the client secret on the server because Spring acts as a confidential OAuth client.

Configuring Spring Boot credentials

Add Spring Security’s OAuth client starter to the application:

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

Keep registration values in application.yml, using placeholders rather than credentials:

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: ${GITHUB_CLIENT_ID}
            client-secret: ${GITHUB_CLIENT_SECRET}
            scope:
              - read:user
              - user:email

The built-in GitHub provider supplies the authorisation and user-information endpoints, so no custom provider declaration is needed. Supply the environment variables before starting Boot, and ensure they remain absent from Git repositories and build artefacts.

During an arvo deployment, the same checks still apply: use separate OAuth apps for local, staging and production environments. For the wider project context, this practical project guidance can help keep the setup consistent with established conventions.

Wiring the Spring Security filter chain

Keep public pages available while protecting application routes:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/", "/public/**", "/error").permitAll()
            .requestMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated())
        .oauth2Login(Customizer.withDefaults())
        .logout(logout -> logout.logoutSuccessUrl("/").permitAll());

    return http.build();
}

The login flow starts at /oauth2/authorization/github. Spring redirects the browser to GitHub, exchanges the returned code for an access token and creates an authenticated session. A custom login page can link users to that authorisation endpoint.

Identity and access decisions differ depending on whether the application manages its own credentials:

Concern Local passwords GitHub OAuth2
Credential storage Hashed password in the application database Client secret on the server; the GitHub password is never stored
Identity Application-controlled username or email Stable GitHub numeric id
First login Administrator or registration flow creates the account The application can provision a local profile after successful authentication
Revocation Disable the account or reset its credentials Revoke GitHub access and invalidate the local session
Roles Stored in local role records Assigned locally after the provider identity is verified

Mapping GitHub identities to local users

GitHub usernames can change, so use the numeric id as the provider identifier. A custom OAuth2UserService can attach application authorities:

@Bean
OAuth2UserService<OAuth2UserRequest, OAuth2User> githubUsers() {
    OAuth2UserService<OAuth2UserRequest, OAuth2User> loader =
        new DefaultOAuth2UserService();

    return request -> {
        OAuth2User user = loader.loadUser(request);
        Set<GrantedAuthority> authorities =
            new HashSet<>(user.getAuthorities());
        authorities.add(new SimpleGrantedAuthority("ROLE_GITHUB_USER"));

        return new DefaultOAuth2User(
            authorities, user.getAttributes(), "id");
    };
}

Wire the service into oauth2Login through userInfoEndpoint, then persist the provider name, provider user ID, display name and email in local tables. Enforce a unique constraint across provider and providerUserId to prevent duplicate accounts.

Use verified email addresses carefully when linking accounts, and retrieve them from GitHub’s email endpoint rather than assuming every profile contains one. GitHub organisation membership is not a default scope, so privileged roles should come from local role records or a separately authorised server-side membership check.

Hardening access for production

Retain Spring Security’s CSRF protection and use HTTPS with secure session cookies in production. OAuth2 login validates the authorisation response, but it does not continuously check whether GitHub has since revoked access, so provide a way to invalidate local sessions. Avoid persisting access tokens unless the application genuinely needs them, request only necessary scopes and never place the client secret in browser code.

Canberra public-sector buyers may require documented data flows and clear audit procedures. For Australian organisations, align logging and retention with the Privacy Act 1988 and internal security policies. Deploying compute in AWS’s ap-southeast-2 region can support Sydney-based latency and resilience requirements, although the final hosting decision should follow the organisation’s risk assessment.