Skip to content

Commit

Permalink
feat: Add validateAccessToken function to providers (#701)
Browse files Browse the repository at this point in the history
* Add validateAccessToken function to providers to allow verifying the access token received from the providers

* Update CHANGELOG

* Update version and CHANGELOG

* Update CHANGELOG

* Refactor based on PR comments

* Add tests
  • Loading branch information
nkshah2 authored Sep 29, 2023
1 parent 5eb5a0f commit 9ff8fb8
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 6 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

## [16.2.0] - 2023-09-29

### Changes

- Added `validateAccessToken` to the configuration for social login providers, this function allows you to verify the access token returned by the social provider. If you are using Github as a provider, there is a default implmentation provided for this function.

## [16.1.0] - 2023-09-26

- Added `twitter` as a built-in thirdparty provider
Expand Down
7 changes: 7 additions & 0 deletions lib/build/recipe/thirdparty/providers/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,13 @@ function NewProvider(input) {
});
}
}
if (impl.config.validateAccessToken !== undefined && accessToken !== undefined) {
await impl.config.validateAccessToken({
accessToken: accessToken,
clientConfig: impl.config,
userContext,
});
}
if (accessToken && impl.config.userInfoEndpoint !== undefined) {
const headers = {
Authorization: "Bearer " + accessToken,
Expand Down
27 changes: 27 additions & 0 deletions lib/build/recipe/thirdparty/providers/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,33 @@ function Github(input) {
if (input.config.tokenEndpoint === undefined) {
input.config.tokenEndpoint = "https://github.com/login/oauth/access_token";
}
if (input.config.validateAccessToken === undefined) {
input.config.validateAccessToken = async ({ accessToken, clientConfig }) => {
const basicAuthToken = Buffer.from(
`${clientConfig.clientId}:${clientConfig.clientSecret === undefined ? "" : clientConfig.clientSecret}`
).toString("base64");
const applicationsResponse = await cross_fetch_1.default(
`https://api.github.com/applications/${clientConfig.clientId}/token`,
{
headers: {
Authorization: `Basic ${basicAuthToken}`,
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
access_token: accessToken,
}),
}
);
if (applicationsResponse.status !== 200) {
throw new Error("Invalid access token");
}
const body = await applicationsResponse.json();
if (body.app === undefined || body.app.client_id !== clientConfig.clientId) {
throw new Error("Access token does not belong to your application");
}
};
}
const oOverride = input.override;
input.override = function (originalImplementation) {
const oGetConfig = originalImplementation.getConfigForClientType;
Expand Down
15 changes: 15 additions & 0 deletions lib/build/recipe/thirdparty/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ declare type CommonProviderConfig = {
clientConfig: ProviderConfigForClientType;
userContext: any;
}) => Promise<void>;
/**
* This function is responsible for validating the access token received from the third party provider.
* This check can include checking the expiry of the access token, checking the audience of the access token, etc.
*
* This function should throw an error if the access token should be considered invalid, or return nothing if it is valid
*
* @param input.accessToken The access token to be validated
* @param input.clientConfig The configuration provided for the third party provider when initialising SuperTokens
* @param input.userContext Refer to https://supertokens.com/docs/thirdparty/advanced-customizations/user-context
*/
validateAccessToken?: (input: {
accessToken: string;
clientConfig: ProviderConfigForClientType;
userContext: any;
}) => Promise<void>;
requireEmail?: boolean;
generateFakeEmail?: (input: { thirdPartyUserId: string; tenantId: string; userContext: any }) => Promise<string>;
};
Expand Down
2 changes: 1 addition & 1 deletion lib/build/version.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/build/version.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions lib/ts/recipe/thirdparty/providers/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,14 @@ export default function NewProvider(input: ProviderInput): TypeProvider {
}
}

if (impl.config.validateAccessToken !== undefined && accessToken !== undefined) {
await impl.config.validateAccessToken({
accessToken: accessToken,
clientConfig: impl.config,
userContext,
});
}

if (accessToken && impl.config.userInfoEndpoint !== undefined) {
const headers: { [key: string]: string } = {
Authorization: "Bearer " + accessToken,
Expand Down
32 changes: 32 additions & 0 deletions lib/ts/recipe/thirdparty/providers/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,38 @@ export default function Github(input: ProviderInput): TypeProvider {
input.config.tokenEndpoint = "https://github.com/login/oauth/access_token";
}

if (input.config.validateAccessToken === undefined) {
input.config.validateAccessToken = async ({ accessToken, clientConfig }) => {
const basicAuthToken = Buffer.from(
`${clientConfig.clientId}:${clientConfig.clientSecret === undefined ? "" : clientConfig.clientSecret}`
).toString("base64");

const applicationsResponse = await fetch(
`https://api.github.com/applications/${clientConfig.clientId}/token`,
{
headers: {
Authorization: `Basic ${basicAuthToken}`,
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
access_token: accessToken,
}),
}
);

if (applicationsResponse.status !== 200) {
throw new Error("Invalid access token");
}

const body = await applicationsResponse.json();

if (body.app === undefined || body.app.client_id !== clientConfig.clientId) {
throw new Error("Access token does not belong to your application");
}
};
}

const oOverride = input.override;

input.override = function (originalImplementation) {
Expand Down
15 changes: 15 additions & 0 deletions lib/ts/recipe/thirdparty/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ type CommonProviderConfig = {
clientConfig: ProviderConfigForClientType;
userContext: any;
}) => Promise<void>;
/**
* This function is responsible for validating the access token received from the third party provider.
* This check can include checking the expiry of the access token, checking the audience of the access token, etc.
*
* This function should throw an error if the access token should be considered invalid, or return nothing if it is valid
*
* @param input.accessToken The access token to be validated
* @param input.clientConfig The configuration provided for the third party provider when initialising SuperTokens
* @param input.userContext Refer to https://supertokens.com/docs/thirdparty/advanced-customizations/user-context
*/
validateAccessToken?: (input: {
accessToken: string;
clientConfig: ProviderConfigForClientType;
userContext: any;
}) => Promise<void>;
requireEmail?: boolean;
generateFakeEmail?: (input: { thirdPartyUserId: string; tenantId: string; userContext: any }) => Promise<string>;
};
Expand Down
2 changes: 1 addition & 1 deletion lib/ts/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
export const version = "16.1.0";
export const version = "16.2.0";

export const cdiSupported = ["4.0"];

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "supertokens-node",
"version": "16.1.0",
"version": "16.2.0",
"description": "NodeJS driver for SuperTokens core",
"main": "index.js",
"scripts": {
Expand Down
Loading

0 comments on commit 9ff8fb8

Please sign in to comment.