Skip to content

Instantly share code, notes, and snippets.

@feroult
Last active November 22, 2017 18:00
Show Gist options
  • Save feroult/92fe8764d1dddcb3599397e5f44140f9 to your computer and use it in GitHub Desktop.
Save feroult/92fe8764d1dddcb3599397e5f44140f9 to your computer and use it in GitHub Desktop.
Firebase 3.0 + GAE
public class AppengineFirebaseAuth {
private static final String APP_ID = "YOUR_APP_ID";
public static AppengineFirebaseToken verifyIdToken(String token) {
Map<String, String> publicKeys = GooglePublicKeys.getKeys();
for (String kid : publicKeys.keySet()) {
String publicKey = publicKeys.get(kid);
try {
Jws<Claims> claimsJws = verifyIdTokey(token, kid, publicKey);
return new AppengineFirebaseToken(claimsJws.getBody());
} catch (SignatureException e) {
continue; // try next key
} catch (ExpiredJwtException e) {
throw new InvalidFirebaseTokenException(e.getMessage());
}
}
throw new InvalidFirebaseTokenException("No Google public keys for JWT Token");
}
private static Jws<Claims> verifyIdTokey(String token, String kid, String publicKey) {
PublicKey pk = createPk(publicKey);
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(pk).parseClaimsJws(token);
validate(claimsJws, kid);
return claimsJws;
}
private static void validate(Jws<Claims> claimsJws, String kid) {
if (claimsJws.getHeader().getAlgorithm().equals("RS256") &&
claimsJws.getBody().getAudience().equals(APP_ID) &&
claimsJws.getBody().getIssuer().equals("https://securetoken.google.com/" + APP_ID) &&
claimsJws.getBody().getSubject() != null &&
claimsJws.getHeader().getKeyId().equals(kid)) {
return;
}
throw new InvalidFirebaseTokenException("Invalid Firebase Id Token");
}
private static PublicKey createPk(String publicKey) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream stream = new ByteArrayInputStream(publicKey.getBytes("UTF-8"));
java.security.cert.Certificate cert = cf.generateCertificate(stream);
return cert.getPublicKey();
} catch (CertificateException | UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
public class AppengineFirebaseToken {
private Claims claims;
private String uid;
private String issuer;
private String name;
private String picture;
private String email;
private String emailVerified;
public AppengineFirebaseToken(Claims claims) {
this.claims = claims;
}
public String getUid() {
return (String) claims.get("user_id");
}
public String getIssuer() {
return (String) claims.get("iss");
}
public String getName() {
return (String) claims.get("name");
}
public String getPicture() {
return (String) claims.get("picture");
}
public String getEmail() {
return (String) claims.get("email");
}
public boolean isEmailVerified() {
return Boolean.valueOf((String) claims.get("email_verified"));
}
}
public class GooglePublicKeys {
private static final String GOOGLE_PUBLIC_KEYS = "https://www.googleapis.com/robot/v1/metadata/x509/[email protected]";
private static final int ONE_DAY = 1000 * 60 * 60 * 24;
private static GooglePublicKeys instance;
private long timestamp;
private Map<String, String> keys;
private GooglePublicKeys() {
}
public static Map<String, String> getKeys() {
if (instance == null || isOutdated()) {
reload();
}
return instance.keys;
}
private static boolean isOutdated() {
return instance.timestamp < System.currentTimeMillis() - ONE_DAY;
}
private synchronized static void reload() {
if (instance != null && !isOutdated()) {
return;
}
instance = new GooglePublicKeys();
instance.keys = fetchGooglePublicKeys();
instance.timestamp = System.currentTimeMillis();
}
private static Map<String, String> fetchGooglePublicKeys() {
return parseJson(fetchKeysJson());
}
private static String fetchKeysJson() {
try {
URLFetchService urlFetch = URLFetchServiceFactory.getURLFetchService();
HTTPResponse response = urlFetch.fetch(new URL(GOOGLE_PUBLIC_KEYS));
return new String(response.getContent());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static Map<String, String> parseJson(String json) {
Gson gson = new Gson();
Map<String, String> map = new HashMap<>();
return gson.fromJson(json, map.getClass());
}
}
public class InvalidFirebaseTokenException extends RuntimeException {
public InvalidFirebaseTokenException(String message) {
super(message);
}
}
@nickmeinhold
Copy link

This works beautifully, thank you so much!

@jasiekmiko
Copy link

For anyone viewing this gist in isolation, it depends on the JJWT Library as the author explains in his blog post

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