Skip to content

Commit 6c999a4

Browse files
committed
refactor code and cleanup as per feedback
1 parent b8487ba commit 6c999a4

File tree

2 files changed

+43
-17
lines changed

2 files changed

+43
-17
lines changed

appcheck/appcheck.go

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ var JWKSUrl = "https://firebaseappcheck.googleapis.com/v1beta/jwks"
3636

3737
const appCheckIssuer = "https://firebaseappcheck.googleapis.com/"
3838

39+
const tokenVerificationUrlFormat = "https://firebaseappcheck.googleapis.com/v1beta/projects/%s:verifyAppCheckToken"
40+
3941
var (
4042
// ErrIncorrectAlgorithm is returned when the token is signed with a non-RSA256 algorithm.
4143
ErrIncorrectAlgorithm = errors.New("token has incorrect algorithm")
@@ -70,9 +72,9 @@ type DecodedAppCheckToken struct {
7072

7173
// Client is the interface for the Firebase App Check service.
7274
type Client struct {
73-
projectID string
74-
jwks *keyfunc.JWKS
75-
verifyAppCheckTokenURL string
75+
projectID string
76+
jwks *keyfunc.JWKS
77+
tokenVerificationUrl string
7678
}
7779

7880
// NewClient creates a new instance of the Firebase App Check Client.
@@ -90,9 +92,9 @@ func NewClient(ctx context.Context, conf *internal.AppCheckConfig) (*Client, err
9092
}
9193

9294
return &Client{
93-
projectID: conf.ProjectID,
94-
jwks: jwks,
95-
verifyAppCheckTokenURL: fmt.Sprintf("%sv1beta/projects/%s:verifyAppCheckToken", appCheckIssuer, conf.ProjectID),
95+
projectID: conf.ProjectID,
96+
jwks: jwks,
97+
tokenVerificationUrl: fmt.Sprintf(tokenVerificationUrlFormat, conf.ProjectID),
9698
}, nil
9799
}
98100

@@ -174,10 +176,34 @@ func (c *Client) VerifyToken(token string) (*DecodedAppCheckToken, error) {
174176
return &appCheckToken, nil
175177
}
176178

177-
// VerifyTokenWithReplayProtection checks the given App Check token as follows:
178-
// - Uses VerifyToken to validate the given token as described. if verification failed, appropriate error will be returned.
179-
// - Checks if the token token has been consumed. if already consumed the pointer to decoded token is returned with ErrTokenAlreadyConsumed.
180-
func (c *Client) VerifyTokenWithReplayProtection(token string) (*DecodedAppCheckToken, error) {
179+
// VerifyOneTimeToken verifies the given App Check token and consumes it, so that it cannot be consumed again.
180+
//
181+
// VerifyOneTimeToken considers an App Check token string to be valid if all the following conditions are met:
182+
// - The token string is a valid RS256 JWT.
183+
// - The JWT contains valid issuer (iss) and audience (aud) claims that match the issuerPrefix
184+
// and projectID of the tokenVerifier.
185+
// - The JWT contains a valid subject (sub) claim.
186+
// - The JWT is not expired, and it has been issued some time in the past.
187+
// - The JWT is signed by a Firebase App Check backend server as determined by the keySource.
188+
//
189+
// If any of the above conditions are not met, an error is returned, regardless whether the token was
190+
// previously consumed or not.
191+
//
192+
// This method currently only supports App Check tokens exchanged from the following attestation
193+
// providers:
194+
//
195+
// - Play Integrity API
196+
// - Apple App Attest
197+
// - Apple DeviceCheck (DCDevice tokens)
198+
// - reCAPTCHA Enterprise
199+
// - reCAPTCHA v3
200+
// - Custom providers
201+
//
202+
// App Check tokens exchanged from debug secrets are also supported. Calling this method on an
203+
// otherwise valid App Check token with an unsupported provider will cause an error to be returned.
204+
//
205+
// If the token was already consumed prior to this call, an error is returned.
206+
func (c *Client) VerifyOneTimeToken(token string) (*DecodedAppCheckToken, error) {
181207
decodedAppCheckToken, err := c.VerifyToken(token)
182208

183209
if err != nil {
@@ -186,7 +212,7 @@ func (c *Client) VerifyTokenWithReplayProtection(token string) (*DecodedAppCheck
186212

187213
bodyReader := bytes.NewReader([]byte(fmt.Sprintf(`{"app_check_token":%s}`, token)))
188214

189-
resp, err := http.Post(c.verifyAppCheckTokenURL, "application/json", bodyReader)
215+
resp, err := http.Post(c.tokenVerificationUrl, "application/json", bodyReader)
190216

191217
if err != nil {
192218
return nil, err
@@ -203,7 +229,7 @@ func (c *Client) VerifyTokenWithReplayProtection(token string) (*DecodedAppCheck
203229
}
204230

205231
if rb.AlreadyConsumed {
206-
return decodedAppCheckToken, ErrTokenAlreadyConsumed
232+
return nil, ErrTokenAlreadyConsumed
207233
}
208234

209235
return decodedAppCheckToken, nil

appcheck/appcheck_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
"github.com/google/go-cmp/cmp"
1818
)
1919

20-
func TestVerifyTokenWithReplayProtection(t *testing.T) {
20+
func TestVerifyOneTimeToken(t *testing.T) {
2121

2222
projectID := "project_id"
2323

@@ -37,8 +37,8 @@ func TestVerifyTokenWithReplayProtection(t *testing.T) {
3737

3838
jwtToken := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.RegisteredClaims{
3939
Issuer: appCheckIssuer,
40-
Audience: jwt.ClaimStrings([]string{"projects/" + projectID}),
41-
Subject: "12345678:app:ID",
40+
Audience: jwt.ClaimStrings([]string{"projects/12345678", "projects/" + projectID}),
41+
Subject: "1:12345678:android:abcdef",
4242
ExpiresAt: jwt.NewNumericDate(mockTime.Add(time.Hour)),
4343
IssuedAt: jwt.NewNumericDate(mockTime),
4444
NotBefore: jwt.NewNumericDate(mockTime.Add(-1 * time.Hour)),
@@ -79,9 +79,9 @@ func TestVerifyTokenWithReplayProtection(t *testing.T) {
7979
t.Fatalf("error creating new client: %v", err)
8080
}
8181

82-
client.verifyAppCheckTokenURL = appCheckVerifyMockServer.URL
82+
client.tokenVerificationUrl = appCheckVerifyMockServer.URL
8383

84-
_, err = client.VerifyTokenWithReplayProtection(token)
84+
_, err = client.VerifyOneTimeToken(token)
8585

8686
if !errors.Is(err, tt.expectedError) {
8787
t.Errorf("failed to verify token; Expected: %v, but got: %v", tt.expectedError, err)

0 commit comments

Comments
 (0)