Skip to content

feat(appcheck): Verify one-time tokens for replay protection - #774

Open
yvonnep165 wants to merge 6 commits into
devfrom
yp-verify-one-time-token
Open

feat(appcheck): Verify one-time tokens for replay protection#774
yvonnep165 wants to merge 6 commits into
devfrom
yp-verify-one-time-token

Conversation

@yvonnep165

@yvonnep165 yvonnep165 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

This PR introduces the VerifyOneTimeToken method to the appcheck package, enabling stateful replay protection for Firebase App Check.

  • Implement a new method VerifyOneTimeToken that first performs local, stateless JWT verification followed by a stateful POST request to backend endpoint to consume the token.
  • Add the AlreadyConsumed (*bool) field to the DecodedAppCheckToken struct.
  • Plumb option.ClientOption from the main app initialization down to internal.AppCheckConfig.
  • Add unit tests.

@yvonnep165 yvonnep165 changed the title feat(appcheck): verify one-time tokens for replay protection feat(appcheck): Verify one-time tokens for replay protection Aug 10, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the VerifyOneTimeToken method to the App Check client, enabling stateful verification and consumption of one-time tokens. It also adds the AlreadyConsumed field to DecodedAppCheckToken and integrates HTTP client options. Feedback on the tests suggests using a helper function to instantiate *bool pointers to reduce verbosity, and restoring the mutated global verifyURLFormat variable to prevent potential race conditions and side effects.

Comment thread appcheck/appcheck_test.go
Comment on lines +326 to +344
tests := []struct {
name string
backendResponse string
backendStatus int
wantAlreadyConsumed *bool
wantErr bool
}{
{
name: "success_not_consumed",
backendResponse: `{"alreadyConsumed": false}`,
backendStatus: http.StatusOK,
wantAlreadyConsumed: func() *bool { b := false; return &b }(),
},
{
name: "success_already_consumed",
backendResponse: `{"alreadyConsumed": true}`,
backendStatus: http.StatusOK,
wantAlreadyConsumed: func() *bool { b := true; return &b }(),
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using inline anonymous functions to create *bool values is verbose and repetitive. Defining a simple helper function boolPtr makes the test cases much cleaner and more readable.

	boolPtr := func(b bool) *bool { return &b }
	tests := []struct {
		name                string
		backendResponse     string
		backendStatus       int
		wantAlreadyConsumed *bool
		wantErr             bool
	}{
		{
			name:                "success_not_consumed",
			backendResponse:     "{\"alreadyConsumed\": false}",
			backendStatus:       http.StatusOK,
			wantAlreadyConsumed: boolPtr(false),
		},
		{
			name:                "success_already_consumed",
			backendResponse:     "{\"alreadyConsumed\": true}",
			backendStatus:       http.StatusOK,
			wantAlreadyConsumed: boolPtr(true),
		},

Comment thread appcheck/appcheck_test.go
}))
defer backend.Close()

verifyURLFormat = backend.URL + "/v1beta/projects/%s:verifyAppCheckToken"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Mutating the global variable verifyURLFormat inside the test loop can lead to race conditions if tests are run in parallel. Additionally, it leaves the global variable mutated after the test finishes, which can affect other tests in the same package. Consider restoring the original value of verifyURLFormat using defer.

			oldVerifyURLFormat := verifyURLFormat
			defer func() { verifyURLFormat = oldVerifyURLFormat }()
			verifyURLFormat = backend.URL + "/v1beta/projects/%s:verifyAppCheckToken"

@yvonnep165 yvonnep165 self-assigned this Aug 10, 2026
@yvonnep165 yvonnep165 added release-note release:stage Stage a release candidate labels Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:stage Stage a release candidate release-note

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant