Skip to content

Commit 216e197

Browse files
committed
define app check specific client error codes
1 parent 339e395 commit 216e197

6 files changed

Lines changed: 241 additions & 16 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.appcheck;
18+
19+
/**
20+
* Error codes that can be raised by the Firebase App Check APIs.
21+
*/
22+
public enum AppCheckErrorCode {
23+
24+
/**
25+
* One or more arguments specified in the request were invalid.
26+
*/
27+
INVALID_ARGUMENT,
28+
29+
/**
30+
* The provided App Check token is invalid or malformed.
31+
*/
32+
INVALID_APP_CHECK_TOKEN,
33+
34+
/**
35+
* The provided App Check token has expired.
36+
*/
37+
APP_CHECK_TOKEN_EXPIRED,
38+
39+
/**
40+
* Internal error encountered during App Check token verification.
41+
*/
42+
INTERNAL_ERROR,
43+
44+
/**
45+
* App Check verification service is temporarily unavailable or returned an error.
46+
*/
47+
SERVICE_ERROR,
48+
}

‎src/main/java/com/google/firebase/appcheck/FirebaseAppCheckException.java‎

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,28 +28,45 @@
2828
*/
2929
public class FirebaseAppCheckException extends FirebaseException {
3030

31+
private final AppCheckErrorCode errorCode;
32+
3133
public FirebaseAppCheckException(
3234
@NonNull ErrorCode errorCode,
3335
@NonNull String message,
3436
@Nullable Throwable cause,
35-
@Nullable IncomingHttpResponse response) {
37+
@Nullable IncomingHttpResponse response,
38+
@Nullable AppCheckErrorCode appCheckErrorCode) {
3639
super(errorCode, message, cause, response);
40+
this.errorCode = appCheckErrorCode;
41+
}
42+
43+
public FirebaseAppCheckException(
44+
@NonNull ErrorCode errorCode,
45+
@NonNull String message,
46+
@Nullable Throwable cause,
47+
@Nullable IncomingHttpResponse response) {
48+
this(errorCode, message, cause, response, null);
3749
}
3850

3951
public FirebaseAppCheckException(
4052
@NonNull ErrorCode errorCode,
4153
@NonNull String message,
4254
@Nullable Throwable cause) {
43-
this(errorCode, message, cause, null);
55+
this(errorCode, message, cause, null, null);
4456
}
4557

4658
public FirebaseAppCheckException(
4759
@NonNull ErrorCode errorCode,
4860
@NonNull String message) {
49-
this(errorCode, message, null, null);
61+
this(errorCode, message, null, null, null);
5062
}
5163

5264
public FirebaseAppCheckException(@NonNull FirebaseException base) {
53-
this(base.getErrorCode(), base.getMessage(), base.getCause(), base.getHttpResponse());
65+
this(base.getErrorCode(), base.getMessage(), base.getCause(), base.getHttpResponse(), null);
66+
}
67+
68+
@Nullable
69+
public AppCheckErrorCode getAppCheckErrorCode() {
70+
return errorCode;
5471
}
5572
}

‎src/main/java/com/google/firebase/appcheck/internal/AppCheckTokenVerifier.java‎

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.google.firebase.ErrorCode;
3232
import com.google.firebase.FirebaseApp;
3333
import com.google.firebase.ImplFirebaseTrampolines;
34+
import com.google.firebase.appcheck.AppCheckErrorCode;
3435
import com.google.firebase.appcheck.DecodedAppCheckToken;
3536
import com.google.firebase.appcheck.FirebaseAppCheckException;
3637
import com.google.firebase.appcheck.VerifyAppCheckTokenOptions;
@@ -146,22 +147,34 @@ private DecodedAppCheckToken verifyTokenLocally(String token) throws FirebaseApp
146147
return new DecodedAppCheckToken(claims.getClaims());
147148
} catch (ParseException e) {
148149
throw new FirebaseAppCheckException(
149-
ErrorCode.INVALID_ARGUMENT, "Failed to parse App Check JWT token: " + e.getMessage(), e);
150+
ErrorCode.INVALID_ARGUMENT,
151+
"Failed to parse App Check JWT token: " + e.getMessage(),
152+
e,
153+
null,
154+
AppCheckErrorCode.INVALID_APP_CHECK_TOKEN);
150155
} catch (ExpiredJWTException e) {
151156
throw new FirebaseAppCheckException(
152-
ErrorCode.INVALID_ARGUMENT, "Firebase App Check token has expired.", e);
157+
ErrorCode.INVALID_ARGUMENT,
158+
"Firebase App Check token has expired.",
159+
e,
160+
null,
161+
AppCheckErrorCode.APP_CHECK_TOKEN_EXPIRED);
153162
} catch (BadJOSEException e) {
154163
throw new FirebaseAppCheckException(
155164
ErrorCode.INVALID_ARGUMENT,
156165
"Check your project: " + projectId + ". Failed to verify App Check token signature: "
157166
+ e.getMessage(),
158-
e);
167+
e,
168+
null,
169+
AppCheckErrorCode.INVALID_APP_CHECK_TOKEN);
159170
} catch (JOSEException e) {
160171
throw new FirebaseAppCheckException(
161172
ErrorCode.INTERNAL,
162173
"Check your project: " + projectId + ". Internal error processing App Check token: "
163174
+ e.getMessage(),
164-
e);
175+
e,
176+
null,
177+
AppCheckErrorCode.INTERNAL_ERROR);
165178
}
166179
}
167180

@@ -172,17 +185,27 @@ private void verifyHeader(JWSHeader header) throws FirebaseAppCheckException {
172185
"App Check token has incorrect algorithm. Expected "
173186
+ JWSAlgorithm.RS256.getName()
174187
+ " but got: "
175-
+ header.getAlgorithm());
188+
+ header.getAlgorithm(),
189+
null,
190+
null,
191+
AppCheckErrorCode.INVALID_APP_CHECK_TOKEN);
176192
}
177193
if (Strings.isNullOrEmpty(header.getKeyID())) {
178194
throw new FirebaseAppCheckException(
179-
ErrorCode.INVALID_ARGUMENT, "App Check token has no 'kid' (key ID) header.");
195+
ErrorCode.INVALID_ARGUMENT,
196+
"App Check token has no 'kid' (key ID) header.",
197+
null,
198+
null,
199+
AppCheckErrorCode.INVALID_APP_CHECK_TOKEN);
180200
}
181201
if (header.getType() == null || !JOSEObjectType.JWT.equals(header.getType())) {
182202
throw new FirebaseAppCheckException(
183203
ErrorCode.INVALID_ARGUMENT,
184204
"App Check token has incorrect 'typ' header. Expected JWT but got: "
185-
+ header.getType());
205+
+ header.getType(),
206+
null,
207+
null,
208+
AppCheckErrorCode.INVALID_APP_CHECK_TOKEN);
186209
}
187210
}
188211

@@ -192,7 +215,11 @@ private void verifyClaims(JWTClaimsSet claims) throws FirebaseAppCheckException
192215

193216
if (Strings.isNullOrEmpty(issuer)) {
194217
throw new FirebaseAppCheckException(
195-
ErrorCode.INVALID_ARGUMENT, "App Check token has no 'iss' (issuer) claim.");
218+
ErrorCode.INVALID_ARGUMENT,
219+
"App Check token has no 'iss' (issuer) claim.",
220+
null,
221+
null,
222+
AppCheckErrorCode.INVALID_APP_CHECK_TOKEN);
196223
}
197224

198225
// The issuer is of the form https://firebaseappcheck.googleapis.com/<project_number>.
@@ -207,7 +234,10 @@ private void verifyClaims(JWTClaimsSet claims) throws FirebaseAppCheckException
207234
"App Check token has incorrect issuer. Expected to start with: "
208235
+ APP_CHECK_ISSUER
209236
+ " but got: "
210-
+ issuer);
237+
+ issuer,
238+
null,
239+
null,
240+
AppCheckErrorCode.INVALID_APP_CHECK_TOKEN);
211241
}
212242

213243
List<String> audience = claims.getAudience();
@@ -218,12 +248,19 @@ private void verifyClaims(JWTClaimsSet claims) throws FirebaseAppCheckException
218248
"App Check token has incorrect audience. Expected to contain: "
219249
+ expectedAudience
220250
+ " but got: "
221-
+ audience);
251+
+ audience,
252+
null,
253+
null,
254+
AppCheckErrorCode.INVALID_APP_CHECK_TOKEN);
222255
}
223256

224257
if (Strings.isNullOrEmpty(claims.getSubject())) {
225258
throw new FirebaseAppCheckException(
226-
ErrorCode.INVALID_ARGUMENT, "App Check token has empty 'sub' (app ID) claim.");
259+
ErrorCode.INVALID_ARGUMENT,
260+
"App Check token has empty 'sub' (app ID) claim.",
261+
null,
262+
null,
263+
AppCheckErrorCode.INVALID_APP_CHECK_TOKEN);
227264
}
228265
}
229266

@@ -260,7 +297,11 @@ private boolean verifyOneTimeToken(String token)
260297
return Boolean.TRUE.equals(alreadyConsumed);
261298
} catch (IOException e) {
262299
throw new FirebaseAppCheckException(
263-
ErrorCode.INTERNAL, "Error verifying App Check token with backend: " + e.getMessage(), e);
300+
ErrorCode.INTERNAL,
301+
"Error verifying App Check token with backend: " + e.getMessage(),
302+
e,
303+
null,
304+
AppCheckErrorCode.SERVICE_ERROR);
264305
} finally {
265306
ApiClientUtils.disconnectQuietly(httpResponse);
266307
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.appcheck;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertNotNull;
21+
22+
import org.junit.Test;
23+
24+
public class AppCheckErrorCodeTest {
25+
26+
@Test
27+
public void testEnum() {
28+
assertNotNull(AppCheckErrorCode.valueOf("INVALID_ARGUMENT"));
29+
assertNotNull(AppCheckErrorCode.valueOf("INVALID_APP_CHECK_TOKEN"));
30+
assertNotNull(AppCheckErrorCode.valueOf("APP_CHECK_TOKEN_EXPIRED"));
31+
assertNotNull(AppCheckErrorCode.valueOf("INTERNAL_ERROR"));
32+
assertNotNull(AppCheckErrorCode.valueOf("SERVICE_ERROR"));
33+
assertEquals(5, AppCheckErrorCode.values().length);
34+
}
35+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.appcheck;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertNull;
21+
import static org.junit.Assert.assertSame;
22+
23+
import com.google.firebase.ErrorCode;
24+
import com.google.firebase.FirebaseException;
25+
import org.junit.Test;
26+
27+
public class FirebaseAppCheckExceptionTest {
28+
29+
@Test
30+
public void testConstructorWithAppCheckErrorCode() {
31+
Exception cause = new Exception("cause");
32+
FirebaseAppCheckException exception =
33+
new FirebaseAppCheckException(
34+
ErrorCode.INVALID_ARGUMENT,
35+
"Invalid token",
36+
cause,
37+
null,
38+
AppCheckErrorCode.APP_CHECK_TOKEN_EXPIRED);
39+
40+
assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode());
41+
assertEquals("Invalid token", exception.getMessage());
42+
assertSame(cause, exception.getCause());
43+
assertNull(exception.getHttpResponse());
44+
assertEquals(AppCheckErrorCode.APP_CHECK_TOKEN_EXPIRED, exception.getAppCheckErrorCode());
45+
}
46+
47+
@Test
48+
public void testConstructorWithoutAppCheckErrorCode() {
49+
FirebaseAppCheckException exception =
50+
new FirebaseAppCheckException(ErrorCode.INTERNAL, "Internal error");
51+
52+
assertEquals(ErrorCode.INTERNAL, exception.getErrorCode());
53+
assertEquals("Internal error", exception.getMessage());
54+
assertNull(exception.getCause());
55+
assertNull(exception.getHttpResponse());
56+
assertNull(exception.getAppCheckErrorCode());
57+
}
58+
59+
@Test
60+
public void testConstructorWithCause() {
61+
Exception cause = new Exception("cause");
62+
FirebaseAppCheckException exception =
63+
new FirebaseAppCheckException(ErrorCode.UNKNOWN, "Unknown error", cause);
64+
65+
assertEquals(ErrorCode.UNKNOWN, exception.getErrorCode());
66+
assertEquals("Unknown error", exception.getMessage());
67+
assertSame(cause, exception.getCause());
68+
assertNull(exception.getAppCheckErrorCode());
69+
}
70+
71+
@Test
72+
public void testConstructorFromFirebaseException() {
73+
FirebaseException base =
74+
new FirebaseException(ErrorCode.UNAVAILABLE, "Service unavailable", null);
75+
FirebaseAppCheckException exception = new FirebaseAppCheckException(base);
76+
77+
assertEquals(ErrorCode.UNAVAILABLE, exception.getErrorCode());
78+
assertEquals("Service unavailable", exception.getMessage());
79+
assertNull(exception.getAppCheckErrorCode());
80+
}
81+
}

‎src/test/java/com/google/firebase/appcheck/internal/AppCheckTokenVerifierTest.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import com.google.firebase.FirebaseApp;
4545
import com.google.firebase.FirebaseOptions;
4646
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
47+
import com.google.firebase.appcheck.AppCheckErrorCode;
4748
import com.google.firebase.appcheck.DecodedAppCheckToken;
4849
import com.google.firebase.appcheck.FirebaseAppCheckException;
4950
import com.google.firebase.appcheck.VerifyAppCheckTokenOptions;
@@ -506,6 +507,7 @@ protected JWKSource<SecurityContext> createKeySource() {
506507
FirebaseAppCheckException ex =
507508
assertThrows(FirebaseAppCheckException.class, () -> realVerifier.verifyToken(token));
508509
assertEquals(ErrorCode.INVALID_ARGUMENT, ex.getErrorCode());
510+
assertEquals(AppCheckErrorCode.INVALID_APP_CHECK_TOKEN, ex.getAppCheckErrorCode());
509511
assertTrue(ex.getMessage().contains("Failed to verify App Check token signature"));
510512
}
511513

@@ -546,6 +548,7 @@ protected JWKSource<SecurityContext> createKeySource() {
546548
FirebaseAppCheckException ex =
547549
assertThrows(FirebaseAppCheckException.class, () -> realVerifier.verifyToken(token));
548550
assertEquals(ErrorCode.INVALID_ARGUMENT, ex.getErrorCode());
551+
assertEquals(AppCheckErrorCode.APP_CHECK_TOKEN_EXPIRED, ex.getAppCheckErrorCode());
549552
assertTrue(ex.getMessage().contains("Firebase App Check token has expired."));
550553
}
551554
}

0 commit comments

Comments
 (0)