Skip to content

Instantly share code, notes, and snippets.

@proton5000
Last active April 15, 2024 09:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save proton5000/672b5e9e81193ff38509384f2e4f7fa2 to your computer and use it in GitHub Desktop.
Save proton5000/672b5e9e81193ff38509384f2e4f7fa2 to your computer and use it in GitHub Desktop.
firebase app check filter (spring boot 3)
package ua.varus.scan.and.go.config.security;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwts;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.Date;
@Component
@RequiredArgsConstructor
public class AppCheckFilter extends OncePerRequestFilter {
private static final String PROJECT_NUMBER = "1050204332425";
private static final String FIREBASE_APP_CHECK_URL = "https://firebaseappcheck.googleapis.com/v1/jwks";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("HTTP_X_FIREBASE_APPCHECK");
if (token == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthenticated");
return;
}
String appId = checkToken(token);
if (appId == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthenticated");
return;
}
request.setAttribute("firebase.app", appId);
filterChain.doFilter(request, response);
}
private String checkToken(String token) {
// Load JWKS keys from a file or URL
try {
// Not necessary to get the public keys each time, it can be cached for 6 hours,
// info from docs https://firebase.google.com/docs/app-check/custom-resource-backend#other
String jwksString = IOUtils.toString(new URL(FIREBASE_APP_CHECK_URL), Charset.defaultCharset());
JWKSet jwks = JWKSet.parse(jwksString);
// Extract JWK with given kid
for (JWK jwk : jwks.toPublicJWKSet().getKeys()) {// Use JWK to verify JWT signature
try {
// If a getting claims not throw the exception it's like You can already trust your mobile application,
// it has a signature, but to clarify that it is your application, you can check the following statements
Claims claims = Jwts.parser()
.verifyWith(jwk.toRSAKey().toRSAPublicKey())
.build()
.parseSignedClaims(token)
.getPayload();
JwsHeader header = Jwts.parser()
.verifyWith(jwk.toRSAKey().toRSAPublicKey())
.build()
.parseSignedClaims(token)
.getHeader();
for (String claimName : claims.keySet()) {
System.out.println(claimName + ": " + claims.get(claimName));
}
// Validate claims, you can choose what is better to validate for your app
if (!header.getAlgorithm().equals("RS256")) {
return null;
}
if (!header.getType().equals("JWT")) {
return null;
}
if (!claims.getIssuer().equals("https://firebaseappcheck.googleapis.com/#" + PROJECT_NUMBER)) {
return null;
}
if (claims.getExpiration().before(new Date())) {
return null;
}
if (!claims.getAudience().contains("projects/" + PROJECT_NUMBER)) {
return null;
}
System.out.println("header -> " + header.getAlgorithm());
System.out.println("type -> " + header.getType());
System.out.println("issuer -> " + claims.getIssuer());
System.out.println("audience -> " + claims.getAudience().contains("projects/" + PROJECT_NUMBER));
System.out.println("expiration -> " + claims.getExpiration().before(new Date()));
System.out.println("subject -> " + claims.getSubject());
System.out.println("Signature is valid");
return claims.getSubject();
} catch (JOSEException e) {
System.out.println("mess_1 -> " + e.getMessage());
System.out.println("Failed to verify signature: " + e.getMessage());
}
}
} catch (IOException | ParseException e) {
System.out.println("mess_2 -> " + e.getMessage());
}
return null;
}
}
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-gson</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-orgjson</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
@proton5000
Copy link
Author

This is how to Verify Firebase App Check tokens from a Java (spring boot 3) back end
https://firebase.google.com/docs/app-check/custom-resource-backend#other

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment