Firebase Authentication provides backend services, easy-to-use SDKs, and ready-made UI libraries to authenticate users to your app. This guide covers complete integration of Firebase Auth in Java applications including server-side verification, user management, and security rules.
Architecture Overview
Java Application → Firebase Admin SDK → Firebase Auth Service → Identity Providers ↑ (JWT Verification / User Management)
Step 1: Dependencies Setup
Maven Dependencies
<!-- pom.xml --> <dependencies> <!-- Firebase Admin SDK --> <dependency> <groupId>com.google.firebase</groupId> <artifactId>firebase-admin</artifactId> <version>9.2.0</version> </dependency> <!-- JWT Processing --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.4.0</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> </dependency> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Validation --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!-- Caching --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> </dependencies>
Step 2: Configuration Classes
Firebase Configuration
// src/main/java/com/company/firebase/config/FirebaseConfig.java
package com.company.firebase.config;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.auth.FirebaseAuth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import javax.annotation.PostConstruct;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@Configuration
@Slf4j
public class FirebaseConfig {
@Value("${firebase.database.url:}")
private String databaseUrl;
@Value("${firebase.project.id:}")
private String projectId;
@Value("${firebase.credentials.file:}")
private String credentialsFile;
@Value("${firebase.credentials.json:}")
private String credentialsJson;
@PostConstruct
public void initialize() {
try {
if (FirebaseApp.getApps().isEmpty()) {
FirebaseOptions options = buildFirebaseOptions();
FirebaseApp.initializeApp(options);
log.info("Firebase application initialized successfully");
} else {
log.info("Firebase application already initialized");
}
} catch (IOException e) {
log.error("Failed to initialize Firebase application", e);
throw new RuntimeException("Firebase initialization failed", e);
}
}
private FirebaseOptions buildFirebaseOptions() throws IOException {
FirebaseOptions.Builder builder = FirebaseOptions.builder();
// Set project ID
if (projectId != null && !projectId.trim().isEmpty()) {
builder.setProjectId(projectId);
}
// Set database URL
if (databaseUrl != null && !databaseUrl.trim().isEmpty()) {
builder.setDatabaseUrl(databaseUrl);
}
// Set credentials
GoogleCredentials credentials = getCredentials();
builder.setCredentials(credentials);
return builder.build();
}
private GoogleCredentials getCredentials() throws IOException {
if (credentialsJson != null && !credentialsJson.trim().isEmpty()) {
// Use JSON string from environment variable or configuration
try (InputStream serviceAccount = new ByteArrayInputStream(credentialsJson.getBytes())) {
return GoogleCredentials.fromStream(serviceAccount);
}
} else if (credentialsFile != null && !credentialsFile.trim().isEmpty()) {
// Use credentials file
Resource resource = new ClassPathResource(credentialsFile);
try (InputStream serviceAccount = resource.getInputStream()) {
return GoogleCredentials.fromStream(serviceAccount);
}
} else {
// Use default credentials (for Google Cloud environments)
return GoogleCredentials.getApplicationDefault();
}
}
@Bean
public FirebaseAuth firebaseAuth() {
return FirebaseAuth.getInstance();
}
}
Application Properties
# application.yml
firebase:
database:
url: ${FIREBASE_DATABASE_URL:https://your-project.firebaseio.com}
project:
id: ${FIREBASE_PROJECT_ID:your-project-id}
credentials:
file: ${FIREBASE_CREDENTIALS_FILE:firebase-service-account.json}
json: ${FIREBASE_CREDENTIALS_JSON:}
auth:
token-expiry: 3600
cookie-name: "firebase-token"
cookie-secure: true
security:
jwt:
secret: ${JWT_SECRET:your-jwt-secret-key}
expiration: 86400000
server:
port: 8080
logging:
level:
com.company.firebase: DEBUG
com.google.firebase: INFO
Step 3: Firebase Auth Service
Core Authentication Service
// src/main/java/com/company/firebase/service/FirebaseAuthService.java
package com.company.firebase.service;
import com.company.firebase.model.FirebaseUser;
import com.google.firebase.auth.*;
import com.google.firebase.auth.UserRecord;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class FirebaseAuthService {
private final FirebaseAuth firebaseAuth;
public FirebaseAuthService(FirebaseAuth firebaseAuth) {
this.firebaseAuth = firebaseAuth;
}
/**
* Verify Firebase ID token and get user information
*/
public FirebaseUser verifyToken(String idToken) {
try {
FirebaseToken decodedToken = firebaseAuth.verifyIdToken(idToken);
FirebaseUser user = new FirebaseUser();
user.setUid(decodedToken.getUid());
user.setEmail(decodedToken.getEmail());
user.setEmailVerified(decodedToken.isEmailVerified());
user.setDisplayName(decodedToken.getName());
user.setPhotoUrl(decodedToken.getPicture());
user.setIssuer(decodedToken.getIssuer());
user.setSignInProvider(decodedToken.getFirebase().getSignInProvider());
user.setClaims(decodedToken.getClaims());
log.info("Token verified for user: {}", user.getUid());
return user;
} catch (FirebaseAuthException e) {
log.error("Firebase token verification failed", e);
throw new RuntimeException("Token verification failed: " + e.getMessage(), e);
}
}
/**
* Verify ID token and check if revoked
*/
public FirebaseUser verifyTokenAndCheckRevoked(String idToken) {
try {
FirebaseToken decodedToken = firebaseAuth.verifyIdToken(idToken, true);
FirebaseUser user = new FirebaseUser();
user.setUid(decodedToken.getUid());
user.setEmail(decodedToken.getEmail());
user.setEmailVerified(decodedToken.isEmailVerified());
user.setClaims(decodedToken.getClaims());
return user;
} catch (FirebaseAuthException e) {
log.error("Firebase token verification failed (with revocation check)", e);
throw new RuntimeException("Token verification failed: " + e.getMessage(), e);
}
}
/**
* Create a new user with email and password
*/
public FirebaseUser createUser(String email, String password, String displayName) {
try {
UserRecord.CreateRequest request = new UserRecord.CreateRequest()
.setEmail(email)
.setPassword(password)
.setDisplayName(displayName)
.setEmailVerified(false);
UserRecord userRecord = firebaseAuth.createUser(request);
return convertToFirebaseUser(userRecord);
} catch (FirebaseAuthException e) {
log.error("Failed to create Firebase user: {}", email, e);
throw new RuntimeException("User creation failed: " + e.getMessage(), e);
}
}
/**
* Create a new user with additional properties
*/
public FirebaseUser createUserWithProperties(UserCreationRequest request) {
try {
UserRecord.CreateRequest createRequest = new UserRecord.CreateRequest()
.setEmail(request.getEmail())
.setPassword(request.getPassword())
.setDisplayName(request.getDisplayName())
.setPhotoUrl(request.getPhotoUrl())
.setEmailVerified(request.isEmailVerified())
.setDisabled(false);
// Set custom claims if provided
if (request.getCustomClaims() != null && !request.getCustomClaims().isEmpty()) {
// Custom claims are set after user creation
}
UserRecord userRecord = firebaseAuth.createUser(createRequest);
// Set custom claims if provided
if (request.getCustomClaims() != null && !request.getCustomClaims().isEmpty()) {
setCustomClaims(userRecord.getUid(), request.getCustomClaims());
}
return convertToFirebaseUser(userRecord);
} catch (FirebaseAuthException e) {
log.error("Failed to create Firebase user with properties: {}", request.getEmail(), e);
throw new RuntimeException("User creation failed: " + e.getMessage(), e);
}
}
/**
* Get user by UID
*/
public FirebaseUser getUserById(String uid) {
try {
UserRecord userRecord = firebaseAuth.getUser(uid);
return convertToFirebaseUser(userRecord);
} catch (FirebaseAuthException e) {
log.error("Failed to get Firebase user: {}", uid, e);
throw new RuntimeException("User retrieval failed: " + e.getMessage(), e);
}
}
/**
* Get user by email
*/
public FirebaseUser getUserByEmail(String email) {
try {
UserRecord userRecord = firebaseAuth.getUserByEmail(email);
return convertToFirebaseUser(userRecord);
} catch (FirebaseAuthException e) {
log.error("Failed to get Firebase user by email: {}", email, e);
throw new RuntimeException("User retrieval failed: " + e.getMessage(), e);
}
}
/**
* Update user properties
*/
public FirebaseUser updateUser(String uid, UserUpdateRequest updateRequest) {
try {
UserRecord.UpdateRequest request = new UserRecord.UpdateRequest(uid);
if (updateRequest.getEmail() != null) {
request.setEmail(updateRequest.getEmail());
}
if (updateRequest.getPassword() != null) {
request.setPassword(updateRequest.getPassword());
}
if (updateRequest.getDisplayName() != null) {
request.setDisplayName(updateRequest.getDisplayName());
}
if (updateRequest.getPhotoUrl() != null) {
request.setPhotoUrl(updateRequest.getPhotoUrl());
}
if (updateRequest.isEmailVerified() != null) {
request.setEmailVerified(updateRequest.isEmailVerified());
}
if (updateRequest.isDisabled() != null) {
request.setDisabled(updateRequest.isDisabled());
}
UserRecord userRecord = firebaseAuth.updateUser(request);
return convertToFirebaseUser(userRecord);
} catch (FirebaseAuthException e) {
log.error("Failed to update Firebase user: {}", uid, e);
throw new RuntimeException("User update failed: " + e.getMessage(), e);
}
}
/**
* Delete user by UID
*/
public void deleteUser(String uid) {
try {
firebaseAuth.deleteUser(uid);
log.info("Firebase user deleted: {}", uid);
} catch (FirebaseAuthException e) {
log.error("Failed to delete Firebase user: {}", uid, e);
throw new RuntimeException("User deletion failed: " + e.getMessage(), e);
}
}
/**
* Set custom claims for a user
*/
public void setCustomClaims(String uid, Map<String, Object> claims) {
try {
firebaseAuth.setCustomUserClaims(uid, claims);
log.info("Custom claims set for user: {}", uid);
} catch (FirebaseAuthException e) {
log.error("Failed to set custom claims for user: {}", uid, e);
throw new RuntimeException("Setting custom claims failed: " + e.getMessage(), e);
}
}
/**
* Get custom claims for a user
*/
public Map<String, Object> getCustomClaims(String uid) {
try {
UserRecord userRecord = firebaseAuth.getUser(uid);
return userRecord.getCustomClaims();
} catch (FirebaseAuthException e) {
log.error("Failed to get custom claims for user: {}", uid, e);
throw new RuntimeException("Getting custom claims failed: " + e.getMessage(), e);
}
}
/**
* Revoke all refresh tokens for a user
*/
public void revokeRefreshTokens(String uid) {
try {
firebaseAuth.revokeRefreshTokens(uid);
log.info("Refresh tokens revoked for user: {}", uid);
} catch (FirebaseAuthException e) {
log.error("Failed to revoke refresh tokens for user: {}", uid, e);
throw new RuntimeException("Revoking tokens failed: " + e.getMessage(), e);
}
}
/**
* List all users with pagination
*/
public List<FirebaseUser> listUsers(int maxResults, String pageToken) {
try {
ListUsersPage page = firebaseAuth.listUsers(null, maxResults);
List<FirebaseUser> users = new ArrayList<>();
for (ExportedUserRecord userRecord : page.iterateAll()) {
users.add(convertToFirebaseUser(userRecord));
}
return users;
} catch (FirebaseAuthException e) {
log.error("Failed to list Firebase users", e);
throw new RuntimeException("Listing users failed: " + e.getMessage(), e);
}
}
/**
* Create custom token for server-to-server authentication
*/
public String createCustomToken(String uid, Map<String, Object> additionalClaims) {
try {
return firebaseAuth.createCustomToken(uid, additionalClaims);
} catch (FirebaseAuthException e) {
log.error("Failed to create custom token for user: {}", uid, e);
throw new RuntimeException("Custom token creation failed: " + e.getMessage(), e);
}
}
/**
* Generate email verification link
*/
public String generateEmailVerificationLink(String email, String redirectUrl) {
try {
ActionCodeSettings settings = ActionCodeSettings.builder()
.setUrl(redirectUrl)
.setHandleCodeInApp(true)
.build();
return firebaseAuth.generateEmailVerificationLink(email, settings);
} catch (FirebaseAuthException e) {
log.error("Failed to generate email verification link for: {}", email, e);
throw new RuntimeException("Email verification link generation failed: " + e.getMessage(), e);
}
}
/**
* Generate password reset link
*/
public String generatePasswordResetLink(String email, String redirectUrl) {
try {
ActionCodeSettings settings = ActionCodeSettings.builder()
.setUrl(redirectUrl)
.setHandleCodeInApp(true)
.build();
return firebaseAuth.generatePasswordResetLink(email, settings);
} catch (FirebaseAuthException e) {
log.error("Failed to generate password reset link for: {}", email, e);
throw new RuntimeException("Password reset link generation failed: " + e.getMessage(), e);
}
}
/**
* Generate email sign-in link
*/
public String generateSignInWithEmailLink(String email, String redirectUrl) {
try {
ActionCodeSettings settings = ActionCodeSettings.builder()
.setUrl(redirectUrl)
.setHandleCodeInApp(true)
.build();
return firebaseAuth.generateSignInWithEmailLink(email, settings);
} catch (FirebaseAuthException e) {
log.error("Failed to generate sign-in link for: {}", email, e);
throw new RuntimeException("Sign-in link generation failed: " + e.getMessage(), e);
}
}
/**
* Import users in batch
*/
public UserImportResult importUsers(List<UserImportRecord> users) {
try {
List<UserRecord.ImportUser> importUsers = new ArrayList<>();
for (UserImportRecord user : users) {
UserRecord.ImportUser importUser = new UserRecord.ImportUser()
.setUid(user.getUid())
.setEmail(user.getEmail())
.setDisplayName(user.getDisplayName())
.setPhotoUrl(user.getPhotoUrl())
.setEmailVerified(user.isEmailVerified())
.setPasswordHash(user.getPasswordHash().getBytes())
.setPasswordSalt(user.getPasswordSalt().getBytes());
importUsers.add(importUser);
}
UserImportResult result = firebaseAuth.importUsers(importUsers);
log.info("Imported {} users successfully", result.getSuccessCount());
return result;
} catch (FirebaseAuthException e) {
log.error("Failed to import users", e);
throw new RuntimeException("User import failed: " + e.getMessage(), e);
}
}
/**
* Convert Firebase UserRecord to FirebaseUser
*/
private FirebaseUser convertToFirebaseUser(UserRecord userRecord) {
FirebaseUser user = new FirebaseUser();
user.setUid(userRecord.getUid());
user.setEmail(userRecord.getEmail());
user.setEmailVerified(userRecord.isEmailVerified());
user.setDisplayName(userRecord.getDisplayName());
user.setPhotoUrl(userRecord.getPhotoUrl());
user.setDisabled(userRecord.isDisabled());
user.setCustomClaims(userRecord.getCustomClaims());
user.setProviderData(userRecord.getProviderData());
user.setTokensValidAfterTimestamp(userRecord.getTokensValidAfterTimestamp());
user.setUserMetadata(new FirebaseUser.UserMetadata(
userRecord.getUserMetadata().getCreationTimestamp(),
userRecord.getUserMetadata().getLastSignInTimestamp()
));
return user;
}
@Data
public static class UserCreationRequest {
private String email;
private String password;
private String displayName;
private String photoUrl;
private boolean emailVerified = false;
private Map<String, Object> customClaims;
}
@Data
public static class UserUpdateRequest {
private String email;
private String password;
private String displayName;
private String photoUrl;
private Boolean emailVerified;
private Boolean disabled;
private Map<String, Object> customClaims;
}
@Data
public static class UserImportRecord {
private String uid;
private String email;
private String displayName;
private String photoUrl;
private boolean emailVerified;
private String passwordHash;
private String passwordSalt;
private Map<String, Object> customClaims;
}
}
Step 4: JWT Token Service
JWT Token Management
// src/main/java/com/company/firebase/service/JwtTokenService.java
package com.company.firebase.service;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.company.firebase.model.FirebaseUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
public class JwtTokenService {
@Value("${security.jwt.secret}")
private String jwtSecret;
@Value("${security.jwt.expiration}")
private long jwtExpiration;
/**
* Generate JWT token for authenticated Firebase user
*/
public String generateToken(FirebaseUser firebaseUser) {
try {
Algorithm algorithm = Algorithm.HMAC256(jwtSecret);
Map<String, Object> claims = new HashMap<>();
claims.put("uid", firebaseUser.getUid());
claims.put("email", firebaseUser.getEmail());
claims.put("emailVerified", firebaseUser.isEmailVerified());
claims.put("provider", firebaseUser.getSignInProvider());
// Add custom claims from Firebase
if (firebaseUser.getClaims() != null) {
claims.putAll(firebaseUser.getClaims());
}
return JWT.create()
.withSubject(firebaseUser.getUid())
.withIssuer("firebase-auth-service")
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + jwtExpiration))
.withClaim("user", claims)
.sign(algorithm);
} catch (Exception e) {
log.error("Failed to generate JWT token for user: {}", firebaseUser.getUid(), e);
throw new RuntimeException("Token generation failed", e);
}
}
/**
* Verify and decode JWT token
*/
public DecodedJWT verifyToken(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(jwtSecret);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("firebase-auth-service")
.build();
return verifier.verify(token);
} catch (JWTVerificationException e) {
log.error("JWT token verification failed", e);
throw new RuntimeException("Token verification failed", e);
}
}
/**
* Extract user information from JWT token
*/
public FirebaseUser extractUserFromToken(String token) {
try {
DecodedJWT decodedJWT = verifyToken(token);
FirebaseUser user = new FirebaseUser();
user.setUid(decodedJWT.getSubject());
Map<String, Object> userClaims = decodedJWT.getClaim("user").asMap();
if (userClaims != null) {
user.setEmail((String) userClaims.get("email"));
user.setEmailVerified(Boolean.TRUE.equals(userClaims.get("emailVerified")));
user.setSignInProvider((String) userClaims.get("provider"));
}
return user;
} catch (Exception e) {
log.error("Failed to extract user from JWT token", e);
throw new RuntimeException("User extraction from token failed", e);
}
}
/**
* Check if token is expired
*/
public boolean isTokenExpired(String token) {
try {
DecodedJWT decodedJWT = verifyToken(token);
return decodedJWT.getExpiresAt().before(new Date());
} catch (Exception e) {
return true;
}
}
/**
* Refresh JWT token
*/
public String refreshToken(String oldToken) {
try {
FirebaseUser user = extractUserFromToken(oldToken);
return generateToken(user);
} catch (Exception e) {
log.error("Failed to refresh JWT token", e);
throw new RuntimeException("Token refresh failed", e);
}
}
/**
* Get token expiration date
*/
public Date getExpirationDate(String token) {
try {
DecodedJWT decodedJWT = verifyToken(token);
return decodedJWT.getExpiresAt();
} catch (Exception e) {
log.error("Failed to get token expiration date", e);
return new Date(); // Return current date as fallback
}
}
/**
* Get token issued date
*/
public Date getIssuedAtDate(String token) {
try {
DecodedJWT decodedJWT = verifyToken(token);
return decodedJWT.getIssuedAt();
} catch (Exception e) {
log.error("Failed to get token issued date", e);
return new Date(); // Return current date as fallback
}
}
}
Step 5: Security Configuration
Spring Security Configuration
// src/main/java/com/company/firebase/config/SecurityConfig.java
package com.company.firebase.config;
import com.company.firebase.security.FirebaseAuthenticationFilter;
import com.company.firebase.security.FirebaseAuthenticationProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
private final FirebaseAuthenticationProvider firebaseAuthenticationProvider;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/public/**", "/api/auth/login", "/api/auth/register").permitAll()
.requestMatchers("/api/auth/admin/**").hasRole("ADMIN")
.requestMatchers("/api/auth/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(firebaseAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public FirebaseAuthenticationFilter firebaseAuthenticationFilter() {
return new FirebaseAuthenticationFilter();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("http://localhost:*", "https://*.yourdomain.com"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Firebase Authentication Provider
// src/main/java/com/company/firebase/security/FirebaseAuthenticationProvider.java
package com.company.firebase.security;
import com.company.firebase.model.FirebaseUser;
import com.company.firebase.service.FirebaseAuthService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
@Slf4j
public class FirebaseAuthenticationProvider implements AuthenticationProvider {
private final FirebaseAuthService firebaseAuthService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof FirebaseAuthenticationToken)) {
return null;
}
FirebaseAuthenticationToken firebaseAuthentication = (FirebaseAuthenticationToken) authentication;
String idToken = firebaseAuthentication.getCredentials().toString();
try {
// Verify Firebase token
FirebaseUser firebaseUser = firebaseAuthService.verifyToken(idToken);
// Extract authorities from custom claims
List<SimpleGrantedAuthority> authorities = extractAuthorities(firebaseUser);
// Create authenticated token
FirebaseAuthenticationToken authenticatedToken =
new FirebaseAuthenticationToken(firebaseUser, idToken, authorities);
authenticatedToken.setAuthenticated(true);
log.debug("User authenticated successfully: {}", firebaseUser.getUid());
return authenticatedToken;
} catch (Exception e) {
log.error("Firebase authentication failed", e);
throw new BadCredentialsException("Authentication failed", e);
}
}
@Override
public boolean supports(Class<?> authentication) {
return FirebaseAuthenticationToken.class.isAssignableFrom(authentication);
}
private List<SimpleGrantedAuthority> extractAuthorities(FirebaseUser firebaseUser) {
Map<String, Object> claims = firebaseUser.getClaims();
if (claims == null || claims.isEmpty()) {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
}
// Extract roles from custom claims
Object rolesClaim = claims.get("roles");
if (rolesClaim instanceof List) {
@SuppressWarnings("unchecked")
List<String> roles = (List<String>) rolesClaim;
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
.collect(Collectors.toList());
}
// Extract role from single claim
Object roleClaim = claims.get("role");
if (roleClaim instanceof String) {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + ((String) roleClaim).toUpperCase()));
}
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
}
}
Firebase Authentication Filter
// src/main/java/com/company/firebase/security/FirebaseAuthenticationFilter.java
package com.company.firebase.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Slf4j
public class FirebaseAuthenticationFilter extends OncePerRequestFilter {
private static final String AUTH_HEADER = "Authorization";
private static final String AUTH_PREFIX = "Bearer ";
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader(AUTH_HEADER);
if (authHeader != null && authHeader.startsWith(AUTH_PREFIX)) {
String idToken = authHeader.substring(AUTH_PREFIX.length());
try {
FirebaseAuthenticationToken authToken = new FirebaseAuthenticationToken(null, idToken);
SecurityContextHolder.getContext().setAuthentication(authToken);
} catch (Exception e) {
log.warn("Firebase token authentication failed", e);
SecurityContextHolder.clearContext();
}
}
filterChain.doFilter(request, response);
}
}
// Firebase Authentication Token
class FirebaseAuthenticationToken extends org.springframework.security.authentication.AbstractAuthenticationToken {
private final Object principal;
private final String credentials;
public FirebaseAuthenticationToken(Object principal, String credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public FirebaseAuthenticationToken(Object principal, String credentials,
java.util.Collection<org.springframework.security.core.GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return credentials;
}
@Override
public Object getPrincipal() {
return principal;
}
}
Step 6: Data Models
Firebase User Model
// src/main/java/com/company/firebase/model/FirebaseUser.java
package com.company.firebase.model;
import com.google.firebase.auth.UserInfo;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class FirebaseUser {
private String uid;
private String email;
private boolean emailVerified;
private String displayName;
private String photoUrl;
private boolean disabled;
private Map<String, Object> customClaims;
private List<? extends UserInfo> providerData;
private Long tokensValidAfterTimestamp;
private UserMetadata userMetadata;
private String issuer;
private String signInProvider;
private Map<String, Object> claims;
@Data
public static class UserMetadata {
private final Long creationTimestamp;
private final Long lastSignInTimestamp;
public UserMetadata(Long creationTimestamp, Long lastSignInTimestamp) {
this.creationTimestamp = creationTimestamp;
this.lastSignInTimestamp = lastSignInTimestamp;
}
}
// Helper methods
public boolean isActive() {
return !disabled && emailVerified;
}
public boolean hasRole(String role) {
if (customClaims == null) return false;
Object roles = customClaims.get("roles");
if (roles instanceof List) {
return ((List<?>) roles).contains(role);
}
return role.equals(customClaims.get("role"));
}
public boolean hasPermission(String permission) {
if (customClaims == null) return false;
Object permissions = customClaims.get("permissions");
if (permissions instanceof List) {
return ((List<?>) permissions).contains(permission);
}
return false;
}
}
Step 7: REST API Controllers
Authentication Controller
// src/main/java/com/company/firebase/controller/AuthController.java
package com.company.firebase.controller;
import com.company.firebase.model.FirebaseUser;
import com.company.firebase.security.FirebaseAuthenticationToken;
import com.company.firebase.service.FirebaseAuthService;
import com.company.firebase.service.JwtTokenService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
@Slf4j
public class AuthController {
private final FirebaseAuthService firebaseAuthService;
private final JwtTokenService jwtTokenService;
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(@RequestBody LoginRequest request) {
try {
// Verify Firebase token
FirebaseUser firebaseUser = firebaseAuthService.verifyToken(request.getIdToken());
// Generate JWT token
String jwtToken = jwtTokenService.generateToken(firebaseUser);
return ResponseEntity.ok(Map.of(
"success", true,
"token", jwtToken,
"user", Map.of(
"uid", firebaseUser.getUid(),
"email", firebaseUser.getEmail(),
"displayName", firebaseUser.getDisplayName(),
"emailVerified", firebaseUser.isEmailVerified()
)
));
} catch (Exception e) {
log.error("Login failed", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Authentication failed"
));
}
}
@PostMapping("/register")
public ResponseEntity<Map<String, Object>> register(@RequestBody RegisterRequest request) {
try {
FirebaseAuthService.UserCreationRequest creationRequest =
new FirebaseAuthService.UserCreationRequest();
creationRequest.setEmail(request.getEmail());
creationRequest.setPassword(request.getPassword());
creationRequest.setDisplayName(request.getDisplayName());
FirebaseUser user = firebaseAuthService.createUserWithProperties(creationRequest);
return ResponseEntity.ok(Map.of(
"success", true,
"user", Map.of(
"uid", user.getUid(),
"email", user.getEmail(),
"displayName", user.getDisplayName()
),
"message", "User created successfully"
));
} catch (Exception e) {
log.error("Registration failed", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Registration failed: " + e.getMessage()
));
}
}
@GetMapping("/profile")
public ResponseEntity<Map<String, Object>> getProfile(@AuthenticationPrincipal FirebaseUser user) {
if (user == null) {
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "User not authenticated"
));
}
return ResponseEntity.ok(Map.of(
"success", true,
"user", Map.of(
"uid", user.getUid(),
"email", user.getEmail(),
"displayName", user.getDisplayName(),
"emailVerified", user.isEmailVerified(),
"photoUrl", user.getPhotoUrl()
)
));
}
@PostMapping("/refresh-token")
public ResponseEntity<Map<String, Object>> refreshToken(@RequestBody RefreshTokenRequest request) {
try {
String newToken = jwtTokenService.refreshToken(request.getToken());
return ResponseEntity.ok(Map.of(
"success", true,
"token", newToken
));
} catch (Exception e) {
log.error("Token refresh failed", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Token refresh failed"
));
}
}
@PostMapping("/verify-email")
public ResponseEntity<Map<String, Object>> sendVerificationEmail(@RequestBody EmailRequest request) {
try {
String verificationLink = firebaseAuthService.generateEmailVerificationLink(
request.getEmail(),
request.getRedirectUrl()
);
// In a real application, you would send this link via email
log.info("Email verification link: {}", verificationLink);
return ResponseEntity.ok(Map.of(
"success", true,
"message", "Verification email sent"
));
} catch (Exception e) {
log.error("Failed to send verification email", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Failed to send verification email"
));
}
}
@PostMapping("/reset-password")
public ResponseEntity<Map<String, Object>> sendPasswordResetEmail(@RequestBody EmailRequest request) {
try {
String resetLink = firebaseAuthService.generatePasswordResetLink(
request.getEmail(),
request.getRedirectUrl()
);
// In a real application, you would send this link via email
log.info("Password reset link: {}", resetLink);
return ResponseEntity.ok(Map.of(
"success", true,
"message", "Password reset email sent"
));
} catch (Exception e) {
log.error("Failed to send password reset email", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Failed to send password reset email"
));
}
}
@GetMapping("/public/health")
public ResponseEntity<Map<String, String>> healthCheck() {
return ResponseEntity.ok(Map.of("status", "OK", "service", "firebase-auth"));
}
// Request DTOs
@Data
public static class LoginRequest {
private String idToken;
}
@Data
public static class RegisterRequest {
private String email;
private String password;
private String displayName;
}
@Data
public static class RefreshTokenRequest {
private String token;
}
@Data
public static class EmailRequest {
private String email;
private String redirectUrl;
}
}
User Management Controller
// src/main/java/com/company/firebase/controller/UserController.java
package com.company.firebase.controller;
import com.company.firebase.model.FirebaseUser;
import com.company.firebase.service.FirebaseAuthService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@Slf4j
public class UserController {
private final FirebaseAuthService firebaseAuthService;
@GetMapping("/me")
public ResponseEntity<Map<String, Object>> getCurrentUser(@AuthenticationPrincipal FirebaseUser user) {
try {
FirebaseUser currentUser = firebaseAuthService.getUserById(user.getUid());
return ResponseEntity.ok(Map.of(
"success", true,
"user", Map.of(
"uid", currentUser.getUid(),
"email", currentUser.getEmail(),
"displayName", currentUser.getDisplayName(),
"emailVerified", currentUser.isEmailVerified(),
"photoUrl", currentUser.getPhotoUrl(),
"disabled", currentUser.isDisabled(),
"metadata", Map.of(
"createdAt", currentUser.getUserMetadata().getCreationTimestamp(),
"lastLogin", currentUser.getUserMetadata().getLastSignInTimestamp()
)
)
));
} catch (Exception e) {
log.error("Failed to get current user", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Failed to get user information"
));
}
}
@PutMapping("/me")
public ResponseEntity<Map<String, Object>> updateCurrentUser(
@AuthenticationPrincipal FirebaseUser currentUser,
@RequestBody UpdateUserRequest request) {
try {
FirebaseAuthService.UserUpdateRequest updateRequest = new FirebaseAuthService.UserUpdateRequest();
updateRequest.setDisplayName(request.getDisplayName());
updateRequest.setPhotoUrl(request.getPhotoUrl());
FirebaseUser updatedUser = firebaseAuthService.updateUser(currentUser.getUid(), updateRequest);
return ResponseEntity.ok(Map.of(
"success", true,
"user", Map.of(
"uid", updatedUser.getUid(),
"displayName", updatedUser.getDisplayName(),
"photoUrl", updatedUser.getPhotoUrl()
),
"message", "User updated successfully"
));
} catch (Exception e) {
log.error("Failed to update user", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Failed to update user"
));
}
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/{uid}")
public ResponseEntity<Map<String, Object>> getUserById(@PathVariable String uid) {
try {
FirebaseUser user = firebaseAuthService.getUserById(uid);
return ResponseEntity.ok(Map.of(
"success", true,
"user", createUserResponse(user)
));
} catch (Exception e) {
log.error("Failed to get user: {}", uid, e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "User not found"
));
}
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping
public ResponseEntity<Map<String, Object>> listUsers(
@RequestParam(defaultValue = "100") int limit,
@RequestParam(required = false) String pageToken) {
try {
List<FirebaseUser> users = firebaseAuthService.listUsers(limit, pageToken);
List<Map<String, Object>> userResponses = users.stream()
.map(this::createUserResponse)
.toList();
return ResponseEntity.ok(Map.of(
"success", true,
"users", userResponses,
"count", userResponses.size()
));
} catch (Exception e) {
log.error("Failed to list users", e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Failed to list users"
));
}
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/{uid}/claims")
public ResponseEntity<Map<String, Object>> setCustomClaims(
@PathVariable String uid,
@RequestBody Map<String, Object> claims) {
try {
firebaseAuthService.setCustomClaims(uid, claims);
return ResponseEntity.ok(Map.of(
"success", true,
"message", "Custom claims set successfully"
));
} catch (Exception e) {
log.error("Failed to set custom claims for user: {}", uid, e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Failed to set custom claims"
));
}
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/{uid}/disable")
public ResponseEntity<Map<String, Object>> disableUser(@PathVariable String uid) {
try {
FirebaseAuthService.UserUpdateRequest updateRequest = new FirebaseAuthService.UserUpdateRequest();
updateRequest.setDisabled(true);
firebaseAuthService.updateUser(uid, updateRequest);
return ResponseEntity.ok(Map.of(
"success", true,
"message", "User disabled successfully"
));
} catch (Exception e) {
log.error("Failed to disable user: {}", uid, e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Failed to disable user"
));
}
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/{uid}/enable")
public ResponseEntity<Map<String, Object>> enableUser(@PathVariable String uid) {
try {
FirebaseAuthService.UserUpdateRequest updateRequest = new FirebaseAuthService.UserUpdateRequest();
updateRequest.setDisabled(false);
firebaseAuthService.updateUser(uid, updateRequest);
return ResponseEntity.ok(Map.of(
"success", true,
"message", "User enabled successfully"
));
} catch (Exception e) {
log.error("Failed to enable user: {}", uid, e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Failed to enable user"
));
}
}
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{uid}")
public ResponseEntity<Map<String, Object>> deleteUser(@PathVariable String uid) {
try {
firebaseAuthService.deleteUser(uid);
return ResponseEntity.ok(Map.of(
"success", true,
"message", "User deleted successfully"
));
} catch (Exception e) {
log.error("Failed to delete user: {}", uid, e);
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"error", "Failed to delete user"
));
}
}
private Map<String, Object> createUserResponse(FirebaseUser user) {
return Map.of(
"uid", user.getUid(),
"email", user.getEmail(),
"displayName", user.getDisplayName(),
"emailVerified", user.isEmailVerified(),
"photoUrl", user.getPhotoUrl(),
"disabled", user.isDisabled(),
"customClaims", user.getCustomClaims(),
"metadata", Map.of(
"createdAt", user.getUserMetadata().getCreationTimestamp(),
"lastLogin", user.getUserMetadata().getLastSignInTimestamp()
)
);
}
@Data
public static class UpdateUserRequest {
private String displayName;
private String photoUrl;
}
}
Step 8: Application Configuration
Main Application Class
// src/main/java/com/company/firebase/FirebaseAuthApplication.java
package com.company.firebase;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class FirebaseAuthApplication {
public static void main(String[] args) {
SpringApplication.run(FirebaseAuthApplication.class, args);
}
}
Best Practices
- Security
- Store Firebase service account credentials securely
- Use environment variables for sensitive configuration
- Implement proper CORS configuration
- Use HTTPS in production
- Performance
- Cache Firebase user information when appropriate
- Use connection pooling for Firebase Admin SDK
- Implement rate limiting for authentication endpoints
- Error Handling
- Implement comprehensive error handling
- Provide meaningful error messages to clients
- Log authentication events for security monitoring
- Scalability
- Use stateless authentication with JWT tokens
- Implement token refresh mechanisms
- Support multiple authentication providers
Conclusion
This Firebase Authentication implementation provides:
- Complete Authentication: Email/password, social providers, and custom tokens
- User Management: Create, read, update, and delete user accounts
- Security Integration: Spring Security integration with Firebase tokens
- Custom Claims: Role-based access control with custom claims
- Token Management: JWT token generation and verification
Key features:
- Multiple Authentication Methods: Support for various Firebase auth providers
- Role-Based Access Control: Custom claims for fine-grained permissions
- Comprehensive User Management: Full CRUD operations for user accounts
- Security Integration: Seamless integration with Spring Security
- Scalable Architecture: Stateless JWT tokens for horizontal scaling
This solution provides a robust foundation for implementing Firebase Authentication in Java applications, suitable for both small projects and enterprise applications.