-
Notifications
You must be signed in to change notification settings - Fork 390
feat(appcheck): Added replay protection feature to App Check verifyToken()
API
#2148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9fb78da
a7013e5
3de33f7
06f0ec3
1b9465b
26fa473
ebc64a7
56a0b98
b893a31
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ import { AppCheckToken } from './app-check-api' | |
|
||
// App Check backend constants | ||
const FIREBASE_APP_CHECK_V1_API_URL_FORMAT = 'https://firebaseappcheck.googleapis.com/v1/projects/{projectId}/apps/{appId}:exchangeCustomToken'; | ||
const ONE_TIME_USE_TOKEN_VERIFICATION_URL_FORMAT = 'https://firebaseappcheck.googleapis.com/v1beta/projects/{projectId}:verifyAppCheckToken'; | ||
|
||
const FIREBASE_APP_CHECK_CONFIG_HEADERS = { | ||
'X-Firebase-Client': `fire-admin-node/${utils.getSdkVersion()}` | ||
|
@@ -86,6 +87,35 @@ export class AppCheckApiClient { | |
}); | ||
} | ||
|
||
public verifyReplayProtection(token: string): Promise<boolean> { | ||
if (!validator.isNonEmptyString(token)) { | ||
throw new FirebaseAppCheckError( | ||
'invalid-argument', | ||
'`token` must be a non-empty string.'); | ||
} | ||
return this.getVerifyTokenUrl() | ||
.then((url) => { | ||
const request: HttpRequestConfig = { | ||
method: 'POST', | ||
url, | ||
headers: FIREBASE_APP_CHECK_CONFIG_HEADERS, | ||
data: { app_check_token: token } | ||
}; | ||
return this.httpClient.send(request); | ||
}) | ||
.then((resp) => { | ||
if (typeof resp.data.alreadyConsumed !== 'undefined' | ||
&& !validator.isBoolean(resp.data?.alreadyConsumed)) { | ||
throw new FirebaseAppCheckError( | ||
'invalid-argument', '`alreadyConsumed` must be a boolean value.'); | ||
} | ||
return resp.data.alreadyConsumed || false; | ||
}) | ||
.catch((err) => { | ||
throw this.toFirebaseError(err); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Just for informational purposes, no action needed.) Right now the backend has two main error conditions (in this order of priority):
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah good point! Since we check the validity of the token prior to making the call to BE, 1) should be addressed already. How do we check 2)?, is it a custom claim encoded in the JWT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For (2), yes, it is actually a private claim named |
||
}); | ||
} | ||
|
||
private getUrl(appId: string): Promise<string> { | ||
return this.getProjectId() | ||
.then((projectId) => { | ||
|
@@ -98,6 +128,17 @@ export class AppCheckApiClient { | |
}); | ||
} | ||
|
||
private getVerifyTokenUrl(): Promise<string> { | ||
return this.getProjectId() | ||
.then((projectId) => { | ||
const urlParams = { | ||
projectId | ||
}; | ||
const baseUrl = utils.formatString(ONE_TIME_USE_TOKEN_VERIFICATION_URL_FORMAT, urlParams); | ||
return utils.formatString(baseUrl); | ||
}); | ||
} | ||
|
||
private getProjectId(): Promise<string> { | ||
if (this.projectId) { | ||
return Promise.resolve(this.projectId); | ||
|
Uh oh!
There was an error while loading. Please reload this page.