diff --git a/.circleci/config.yml b/.circleci/config.yml index d49cb4160..62b4ab496 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,6 @@ version: 2.1 orbs: node: circleci/node@4.1 slack: circleci/slack@3.4.2 - jobs: deploy-to-test-site: docker: @@ -11,7 +10,7 @@ jobs: steps: - checkout - node/install-packages: - app-dir: '~/project/v2' + app-dir: "~/project/v2" - run: cd ../ && git clone git@github.com:supertokens/supertokens-backend-website.git - run: apt-get update - run: apt -y --fix-broken install @@ -26,27 +25,27 @@ jobs: steps: - checkout - node/install-packages: - app-dir: '~/project/v2' + app-dir: "~/project/v2" - run: name: Setup iOS env command: cd v2/src/plugins/codeTypeChecking/iosEnv/ && pod install no_output_timeout: 30m - run: nvm install 16 -y - - run: + - run: no_output_timeout: 30m name: Run build for iOS docs command: nvm use 16 && cd v2 && npm run build:ios - slack/status code-checking: environment: - TAR_OPTIONS: --no-same-owner + TAR_OPTIONS: --no-same-owner docker: - image: rishabhpoddar/supertokens_docs_building_with_android resource_class: xlarge steps: - checkout - node/install-packages: - app-dir: '~/project/v2' + app-dir: "~/project/v2" - run: cd ../ && git clone https://github.com/flutter/flutter.git - run: cd ../ && export PATH="$PATH:/root/flutter/bin" && echo $PATH - run: cd ../ && export PATH="$PATH:/root/flutter/bin" && flutter doctor && chown -R root:root /root/flutter @@ -69,6 +68,26 @@ jobs: name: Setup Dart Env command: cd v2/src/plugins/codeTypeChecking/dart_env && export PATH="$PATH:/root/flutter/bin" && flutter pub get no_output_timeout: 30m + - run: + name: Install Java + command: | + apt-get update + apt-get install -y openjdk-11-jdk + wget https://archive.apache.org/dist/maven/maven-3/3.8.4/binaries/apache-maven-3.8.4-bin.tar.gz + tar xzf apache-maven-3.8.4-bin.tar.gz + mv apache-maven-3.8.4 /opt/maven + echo 'export PATH=/opt/maven/bin:$PATH' >> $BASH_ENV + source $BASH_ENV + - run: + name: Install .NET + command: | + wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + apt-get update + apt-get install -y apt-transport-https + apt-get update + apt-get install -y dotnet-sdk-6.0 - run: command: export PATH="$PATH:/root/flutter/bin" && cd v2 && npm run build no_output_timeout: 30m diff --git a/v2/.gitignore b/v2/.gitignore index b2d6de306..7994bf140 100644 --- a/v2/.gitignore +++ b/v2/.gitignore @@ -18,3 +18,8 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +src/plugins/codeTypeChecking/javaEnv/snippets/* +src/plugins/codeTypeChecking/phpEnv/snippets/* +src/plugins/codeTypeChecking/pythonEnv/snippets/* +src/plugins/codeTypeChecking/csharpEnv/snippets/* diff --git a/v2/attackprotectionsuite/backend-setup.mdx b/v2/attackprotectionsuite/backend-setup.mdx index fd1e36a0e..9dcd94fbb 100644 --- a/v2/attackprotectionsuite/backend-setup.mdx +++ b/v2/attackprotectionsuite/backend-setup.mdx @@ -505,8 +505,8 @@ SuperTokens.init({ const actionType = 'emailpassword-sign-up'; const ip = getIpFromRequest(input.options.req.original); - let email = input.formFields.filter((f) => f.id === "email")[0].value; - let password = input.formFields.filter((f) => f.id === "password")[0].value; + let email = input.formFields.filter((f) => f.id === "email")[0].value as string; + let password = input.formFields.filter((f) => f.id === "password")[0].value as string; const bruteForceConfig = getBruteForceConfig(email, ip, actionType); // we check the anomaly detection service before calling the original implementation of signUp @@ -529,7 +529,7 @@ SuperTokens.init({ const actionType = 'emailpassword-sign-in'; const ip = getIpFromRequest(input.options.req.original); - let email = input.formFields.filter((f) => f.id === "email")[0].value; + let email = input.formFields.filter((f) => f.id === "email")[0].value as string; const bruteForceConfig = getBruteForceConfig(email, ip, actionType); // we check the anomaly detection service before calling the original implementation of signIn @@ -552,7 +552,7 @@ SuperTokens.init({ const actionType = 'send-password-reset-email'; const ip = getIpFromRequest(input.options.req.original); - let email = input.formFields.filter((f) => f.id === "email")[0].value; + let email = input.formFields.filter((f) => f.id === "email")[0].value as string; const bruteForceConfig = getBruteForceConfig(email, ip, actionType); // we check the anomaly detection service before calling the original implementation of generatePasswordResetToken @@ -564,7 +564,7 @@ SuperTokens.init({ return originalImplementation.generatePasswordResetTokenPOST!(input); }, passwordResetPOST: async function (input) { - let password = input.formFields.filter((f) => f.id === "password")[0].value; + let password = input.formFields.filter((f) => f.id === "password")[0].value as string; let securityCheckResponse = await handleSecurityChecks({ password }); if (securityCheckResponse !== undefined) { return securityCheckResponse; diff --git a/v2/community/reusableMD/oauth-paid-banner.mdx b/v2/community/reusableMD/oauth-paid-banner.mdx new file mode 100644 index 000000000..b60cdffa1 --- /dev/null +++ b/v2/community/reusableMD/oauth-paid-banner.mdx @@ -0,0 +1,9 @@ +import CustomAdmonition from "/src/components/customAdmonition" + + + +This is a paid feature. + +You can click on the **Enable Paid Features** button on [our dashboard](https://supertokens.com/dashboard-saas), and follow the steps from there on. Once enabled, this feature is free on the provided development environment. + + diff --git a/v2/community/reusableMD/unified-login-paid-banner.mdx b/v2/community/reusableMD/unified-login-paid-banner.mdx new file mode 100644 index 000000000..e69de29bb diff --git a/v2/docusaurus.config.js b/v2/docusaurus.config.js index 2c8df27dc..7c6d2a02e 100644 --- a/v2/docusaurus.config.js +++ b/v2/docusaurus.config.js @@ -110,7 +110,7 @@ module.exports = { }, prism: { theme: require("prism-react-renderer/themes/vsDark"), - additionalLanguages: ["kotlin", "java", "swift", "dart"], + additionalLanguages: ["kotlin", "java", "swift", "dart", "csharp", "php"], }, algolia: { apiKey: "ce04a158637d345fc094ebbfa9a5156a", @@ -256,6 +256,21 @@ module.exports = { beforeDefaultRemarkPlugins, }, ], + + [ + "@docusaurus/plugin-content-docs", + { + id: "unified-login", + path: "unified-login", + routeBasePath: "docs/unified-login", + sidebarPath: require.resolve("./unified-login/sidebars.js"), + showLastUpdateTime: true, + editUrl: "https://github.com/supertokens/docs/tree/master/v2/", + remarkPlugins: remarkPlugins, + rehypePlugins: rehypePlugins, + beforeDefaultRemarkPlugins, + }, + ], [ "@docusaurus/plugin-content-docs", { diff --git a/v2/emailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx b/v2/emailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx index 1d9752d82..b4763ad81 100644 --- a/v2/emailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx +++ b/v2/emailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx @@ -39,7 +39,7 @@ EmailPassword.init({ return { ...oI, signUpPOST: async function (input) { - let email = input.formFields.find(i => i.id === "email")!.value; + let email = input.formFields.find(i => i.id === "email")!.value as string; if (emailNotAllowed(email)) { // highlight-start @@ -186,4 +186,4 @@ emailpassword.init(override=emailpassword.InputOverrideConfig(apis=override_apis ``` - \ No newline at end of file + diff --git a/v2/emailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/emailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx index fd453819d..d154a65b7 100644 --- a/v2/emailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/emailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -347,7 +347,7 @@ import { backendConfig } from "@/app/config/backend"; SuperTokens.init(backendConfig()); // in the app/api/auth/[...path]/route.ts file -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); const withCustomErrorHandling = async (request: NextRequest) => { try { diff --git a/v2/emailpassword/common-customizations/email-verification/about.mdx b/v2/emailpassword/common-customizations/email-verification/about.mdx index 63b5e5ff1..ceb8fd555 100644 --- a/v2/emailpassword/common-customizations/email-verification/about.mdx +++ b/v2/emailpassword/common-customizations/email-verification/about.mdx @@ -17,6 +17,7 @@ import TabItem from "@theme/TabItem"; import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import AngularUIImplementation from "/src/components/reusableSnippets/angularUIImplementation" import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Enable email verification @@ -25,6 +26,8 @@ import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplement Email verification is turned off by default. It is strongly encouraged to enable it to ensure the authenticity of your users. ::: + + diff --git a/v2/emailpassword/common-customizations/email-verification/protecting-routes.mdx b/v2/emailpassword/common-customizations/email-verification/protecting-routes.mdx index b8759e741..6e13b1a87 100644 --- a/v2/emailpassword/common-customizations/email-verification/protecting-routes.mdx +++ b/v2/emailpassword/common-customizations/email-verification/protecting-routes.mdx @@ -24,11 +24,14 @@ import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import AngularUIImplementation from "/src/components/reusableSnippets/angularUIImplementation" import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" import {Answer} from "/src/components/question" +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting backend APIs and website routes ## Protecting backend API routes + + ### Add email verification checks to all API routes If you want to protect all your backend API routes with email verification checks, set the `mode` to `REQUIRED` in the `EmailVerification` config. Routes protected with the `verifySession` middleware will now additionally check for email verification status. diff --git a/v2/emailpassword/common-customizations/handling-signup-success.mdx b/v2/emailpassword/common-customizations/handling-signup-success.mdx index 3f5ed1349..9d65f2564 100644 --- a/v2/emailpassword/common-customizations/handling-signup-success.mdx +++ b/v2/emailpassword/common-customizations/handling-signup-success.mdx @@ -370,7 +370,7 @@ SuperTokens.init({ let name = "" for (let i = 0; i < input.formFields.length; i++) { if (input.formFields[i].id == "name") { - name = input.formFields[i].value + name = input.formFields[i].value as string; } } @@ -553,4 +553,4 @@ For example, when the frontend calls the email password sign up API, the SDK inv Therefore, if you want to associate a role with a user, you would want to do that in the `Functions.SignUp` function since then those roles would get added to the session in the subsequent call to the `Session.createNewSession` function. If instead, you associate a role to the user in the `API.SignUpPOST` (after calling the original implementation), the role will not be automatically added to the session since the `Session.createNewSession` would have already been called before the original implementation returns. -The only time it makes sense to override the API functions is if you want to access an argument that's not available in the recipe function. For example, the custom form fields for email password sign up is an input to the `API.SignUpPOST`, but not to the `Functions.SignUp`, so if you want to access the form fields, you should override the `API.SignUpPOST` as shown above. You can also always add the formFields to the userContext object and read it later in the `Functions.SignUp` override, but then you would lose the typing of the form field array structure (which is not a runtime problem, but just a slightly bad developer experience). \ No newline at end of file +The only time it makes sense to override the API functions is if you want to access an argument that's not available in the recipe function. For example, the custom form fields for email password sign up is an input to the `API.SignUpPOST`, but not to the `Functions.SignUp`, so if you want to access the form fields, you should override the `API.SignUpPOST` as shown above. You can also always add the formFields to the userContext object and read it later in the `Functions.SignUp` override, but then you would lose the typing of the form field array structure (which is not a runtime problem, but just a slightly bad developer experience). diff --git a/v2/emailpassword/common-customizations/sessions/claims/access-token-payload.mdx b/v2/emailpassword/common-customizations/sessions/claims/access-token-payload.mdx index 3563fef3a..f0290e6cf 100644 --- a/v2/emailpassword/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/emailpassword/common-customizations/sessions/claims/access-token-payload.mdx @@ -22,6 +22,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 1. Using the access token payload +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## Add custom claims to the access token payload :::important diff --git a/v2/emailpassword/common-customizations/sessions/claims/claim-validators.mdx b/v2/emailpassword/common-customizations/sessions/claims/claim-validators.mdx index fdc63bbb1..21f55d69d 100644 --- a/v2/emailpassword/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/emailpassword/common-customizations/sessions/claims/claim-validators.mdx @@ -23,6 +23,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 2. Using claim validators +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## What are session claims? SuperTokens session has a property called `accessTokenPayload`. This is a `JSON` object that's stored in a user's session which can be accessed on the frontend and backend. The key-values in this JSON payload are called claims. diff --git a/v2/emailpassword/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/emailpassword/common-customizations/sessions/protecting-frontend-routes.mdx index 232bba79f..9e5719247 100644 --- a/v2/emailpassword/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/emailpassword/common-customizations/sessions/protecting-frontend-routes.mdx @@ -14,9 +14,12 @@ import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthFrontendVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting frontend routes + + diff --git a/v2/emailpassword/common-customizations/sessions/session-verification-in-api/get-session.mdx b/v2/emailpassword/common-customizations/sessions/session-verification-in-api/get-session.mdx index 94a21fa6d..ce8decce4 100644 --- a/v2/emailpassword/common-customizations/sessions/session-verification-in-api/get-session.mdx +++ b/v2/emailpassword/common-customizations/sessions/session-verification-in-api/get-session.mdx @@ -13,10 +13,13 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session Verification using `getSession` + + If you want to use a non-middleware form of `verifySession`, you can use the `getSession` function. ## Using `getSession` diff --git a/v2/emailpassword/common-customizations/sessions/session-verification-in-api/verify-session.mdx b/v2/emailpassword/common-customizations/sessions/session-verification-in-api/verify-session.mdx index f176eab66..e7a1df653 100644 --- a/v2/emailpassword/common-customizations/sessions/session-verification-in-api/verify-session.mdx +++ b/v2/emailpassword/common-customizations/sessions/session-verification-in-api/verify-session.mdx @@ -10,10 +10,13 @@ import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" ## Verifying a session using the `verifySession` middleware + + For your APIs that require a user to be logged in, use the `verifySession` middleware: diff --git a/v2/emailpassword/common-customizations/sessions/ssr.mdx b/v2/emailpassword/common-customizations/sessions/ssr.mdx index 863e5cb5b..e89210f16 100644 --- a/v2/emailpassword/common-customizations/sessions/ssr.mdx +++ b/v2/emailpassword/common-customizations/sessions/ssr.mdx @@ -17,9 +17,12 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session verification during server side rendering + + :::important Getting access to the session during server side rendering is only possible using cookie-based sessions. This is the default setting, but you have to keep this in mind if you want to switch to header-based sessions. ::: diff --git a/v2/emailpassword/common-customizations/sessions/with-jwt/jwt-verification.mdx b/v2/emailpassword/common-customizations/sessions/with-jwt/jwt-verification.mdx index d1d014c9c..c39494207 100644 --- a/v2/emailpassword/common-customizations/sessions/with-jwt/jwt-verification.mdx +++ b/v2/emailpassword/common-customizations/sessions/with-jwt/jwt-verification.mdx @@ -15,14 +15,18 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import AppInfoForm from "/src/components/appInfoForm"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Manually verify the JWT + + There are three steps in doing session verification using JWTs: - Verify the JWT signature and expiry using a JWT verification library - Check for custom claim values for authorization. - Preventing CSRF attacks in case you are using cookies to store the JWT. + ## Verifying a JWT using a jwt verification library diff --git a/v2/emailpassword/common-customizations/sessions/with-jwt/read-jwt.mdx b/v2/emailpassword/common-customizations/sessions/with-jwt/read-jwt.mdx index 6406c10f2..22a344607 100644 --- a/v2/emailpassword/common-customizations/sessions/with-jwt/read-jwt.mdx +++ b/v2/emailpassword/common-customizations/sessions/with-jwt/read-jwt.mdx @@ -20,9 +20,12 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Fetching the access token string + + ## On the backend diff --git a/v2/emailpassword/common-customizations/username-password/emailpassword-changes.mdx b/v2/emailpassword/common-customizations/username-password/emailpassword-changes.mdx index 4cf48a6a7..35a8378a7 100644 --- a/v2/emailpassword/common-customizations/username-password/emailpassword-changes.mdx +++ b/v2/emailpassword/common-customizations/username-password/emailpassword-changes.mdx @@ -425,7 +425,7 @@ SuperTokens.init({ let response = await original.signUpPOST!(input); if (response.status === "OK") { // sign up successful - let actualEmail = input.formFields.find(i => i.id === "actualEmail")!.value; + let actualEmail = input.formFields.find(i => i.id === "actualEmail")!.value as string; if (actualEmail === "") { // User did not provide an email. // This is possible since we set optional: true @@ -1008,7 +1008,7 @@ SuperTokens.init({ // ...override from previous code snippet... // highlight-start generatePasswordResetTokenPOST: async function (input) { - let emailOrUsername = input.formFields.find(i => i.id === "email")!.value; + let emailOrUsername = input.formFields.find(i => i.id === "email")!.value as string; if (isInputEmail(emailOrUsername)) { let userId = await getUserUsingEmail(emailOrUsername); if (userId !== undefined) { @@ -1030,7 +1030,7 @@ SuperTokens.init({ } } - let username = input.formFields.find(i => i.id === "email")!.value; + let username = input.formFields.find(i => i.id === "email")!.value as string; let superTokensUsers: supertokensTypes.User[] = await SuperTokens.listUsersByAccountInfo(input.tenantId, { email: username }); @@ -1734,4 +1734,4 @@ When building your custom UI on the frontend, pleas ebe sure to pass the `"actua - \ No newline at end of file + diff --git a/v2/emailpassword/custom-ui/enable-email-verification.mdx b/v2/emailpassword/custom-ui/enable-email-verification.mdx index 5900134e0..addfb0e50 100644 --- a/v2/emailpassword/custom-ui/enable-email-verification.mdx +++ b/v2/emailpassword/custom-ui/enable-email-verification.mdx @@ -15,6 +15,7 @@ import {Answer} from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; import TabItem from "@theme/TabItem"; +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; @@ -24,6 +25,8 @@ There are two modes of email verification: - `REQUIRED`: Requires that the user's email is verified before they can access your application's frontend or backend routes (that are protected with a session). - `OPTIONAL`: Adds information about email verification into the session, but leaves it up to you to enforce it on the backend and frontend based on your business logic. + + ## Step 1: Backend setup diff --git a/v2/emailpassword/custom-ui/handling-session-tokens.mdx b/v2/emailpassword/custom-ui/handling-session-tokens.mdx index 5c8cb0294..bdfa1a21f 100644 --- a/v2/emailpassword/custom-ui/handling-session-tokens.mdx +++ b/v2/emailpassword/custom-ui/handling-session-tokens.mdx @@ -9,6 +9,7 @@ hide_title: true import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; @@ -23,6 +24,7 @@ There are two modes ways in which you can use sessions with SuperTokens: Our frontend SDK uses `httpOnly` cookie based session for websites by default as it secures against tokens theft via XSS attacks. For other platform like mobile apps, we use a bearer token in the Authorization header by default. This setting can be changed [as described in the token transfer section](../common-customizations/sessions/token-transfer-method) + ## If using our frontend SDK @@ -41,9 +43,9 @@ Our frontend SDK handles everything for you. You only need to make sure that you Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. -:::note By default, our web SDKs use cookies to provide credentials. -::: + + diff --git a/v2/emailpassword/custom-ui/securing-routes.mdx b/v2/emailpassword/custom-ui/securing-routes.mdx index 00f7e4f61..b33427557 100644 --- a/v2/emailpassword/custom-ui/securing-routes.mdx +++ b/v2/emailpassword/custom-ui/securing-routes.mdx @@ -9,6 +9,7 @@ hide_title: true import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import TabItem from '@theme/TabItem'; import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" @@ -25,6 +26,8 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + + ### Requiring an active session For your APIs that require a user to be logged in, use the `verifySession` middleware @@ -437,7 +440,7 @@ In case of successful session verification, you get access to a `session` object ### Microservice authentication -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/introduction). +For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/unified-login/machine-to-machine-authentication). diff --git a/v2/emailpassword/graphql-integration/backend-setup.mdx b/v2/emailpassword/graphql-integration/backend-setup.mdx index a521f7578..ec91bb6e9 100644 --- a/v2/emailpassword/graphql-integration/backend-setup.mdx +++ b/v2/emailpassword/graphql-integration/backend-setup.mdx @@ -9,11 +9,14 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Backend Setup ## Complete Quick Setup + + @@ -30,6 +33,7 @@ Follow the [frontend and backend custom UI setup guides](../custom-ui/init/front + diff --git a/v2/emailpassword/hasura-integration/with-jwt.mdx b/v2/emailpassword/hasura-integration/with-jwt.mdx index 31e7c262c..e7a2aeb57 100644 --- a/v2/emailpassword/hasura-integration/with-jwt.mdx +++ b/v2/emailpassword/hasura-integration/with-jwt.mdx @@ -23,6 +23,15 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" Using SuperTokens with Hasura requires you to host your own API layer that uses our Backend SDK. If you do not want to host your own server you can use a serverless environment (AWS Lambda for example) to achieve this. ::: +:::caution + +This guide will only work if you are using **SuperTokens Session Tokens**. + +If you are implementing an **OAuth2** based feature, like **Microservice Authentication** or **Unified Login**, the method of adding custom claims is different. +Please check our separate [page](/docs/unified-login/customizations/add-custom-claims-in-tokens) that shows you how to do this. + +::: + ## 1) Complete the setup guides diff --git a/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx b/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx index 45a5a82f5..fa66b1dff 100644 --- a/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx +++ b/v2/emailpassword/migration/account-creation/ep-migration-without-password-hash.mdx @@ -50,7 +50,7 @@ EmailPassword.init({ return { ...originalImplementation, signUpPOST: async function (input) { - let email = input.formFields.find((field) => field.id === "email")!.value; + let email = input.formFields.find((field) => field.id === "email")!.value as string; // Check if the user signing in exists in the external provider if (await doesUserExistInExternalProvider(email)) { // Return status "EMAIL_ALREADY_EXISTS_ERROR" since the user already exists in the external provider @@ -223,8 +223,8 @@ EmailPassword.init({ ...originalImplementation, signInPOST: async function (input) { // Check if an email-password user with the input email exists in SuperTokens - let email = input.formFields.find((field) => field.id === "email")!.value; - let password = input.formFields.find((field) => field.id === "password")!.value; + let email = input.formFields.find((field) => field.id === "email")!.value as string; + let password = input.formFields.find((field) => field.id === "password")!.value as string; let supertokensUsersWithSameEmail = await SuperTokens.listUsersByAccountInfo(input.tenantId, { email: email }, undefined, input.userContext); @@ -582,7 +582,7 @@ EmailPassword.init({ // Add overrides from the previous step generatePasswordResetTokenPOST: async (input) => { // Retrieve the email from the input - let email = input.formFields.find(i => i.id === "email")!.value; + let email = input.formFields.find(i => i.id === "email")!.value as string; // check if user exists in SuperTokens let supertokensUsersWithSameEmail = await SuperTokens.listUsersByAccountInfo(input.tenantId, { @@ -1133,8 +1133,8 @@ EmailPassword.init({ ...originalImplementation, signInPOST: async function (input) { // Check if an email-password user with the input email exists in SuperTokens - let email = input.formFields.find((field) => field.id === "email")!.value; - let password = input.formFields.find((field) => field.id === "password")!.value; + let email = input.formFields.find((field) => field.id === "email")!.value as string; + let password = input.formFields.find((field) => field.id === "password")!.value as string; let supertokensUsersWithSameEmail = await SuperTokens.listUsersByAccountInfo(input.tenantId, { email: email }, undefined, input.userContext); diff --git a/v2/emailpassword/nestjs/guide.mdx b/v2/emailpassword/nestjs/guide.mdx index 4945faeb3..cef6b9c11 100644 --- a/v2/emailpassword/nestjs/guide.mdx +++ b/v2/emailpassword/nestjs/guide.mdx @@ -12,6 +12,7 @@ import BackendDeliveryMethod from "../../passwordless/reusableMD/backendDelivery import AppInfoForm from "/src/components/appInfoForm" import CoreInjector from "/src/components/coreInjector" import {Question, Answer} from "/src/components/question" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # NestJS Integration Guide @@ -338,6 +339,8 @@ bootstrap(); ## Add a session verification guard + + Now that the library is set up, you can add a guard to protect your API. You can scaffold this by running: `nest g guard auth`. In the newly created `auth.guard.ts` file, implement session verification: diff --git a/v2/emailpassword/nextjs/app-directory/protecting-route.mdx b/v2/emailpassword/nextjs/app-directory/protecting-route.mdx index e636f81f0..311b02a7b 100644 --- a/v2/emailpassword/nextjs/app-directory/protecting-route.mdx +++ b/v2/emailpassword/nextjs/app-directory/protecting-route.mdx @@ -9,16 +9,21 @@ hide_title: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import CoreInjector from "/src/components/coreInjector" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 4. Checking for sessions in frontend routes + + Protecting a website route means that it cannot be accessed unless a user is signed in. If a non signed in user tries to access it, they will be redirected to the login page. + + ## Sessions with Client Components Lets create a client component for the `/` route of our website. diff --git a/v2/emailpassword/nextjs/app-directory/session-verification-middleware.mdx b/v2/emailpassword/nextjs/app-directory/session-verification-middleware.mdx index a76ddce0d..4e4742164 100644 --- a/v2/emailpassword/nextjs/app-directory/session-verification-middleware.mdx +++ b/v2/emailpassword/nextjs/app-directory/session-verification-middleware.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # Using the Next.js middleware + + :::important This method is an alternative method for using sessions in an API. If you are already using [session guards](./session-verification-session-guard.mdx), you can skip this step. ::: diff --git a/v2/emailpassword/nextjs/app-directory/session-verification-session-guard.mdx b/v2/emailpassword/nextjs/app-directory/session-verification-session-guard.mdx index fb03ca4e0..663193a00 100644 --- a/v2/emailpassword/nextjs/app-directory/session-verification-session-guard.mdx +++ b/v2/emailpassword/nextjs/app-directory/session-verification-session-guard.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # Adding a session guard to each API route + + :::note This is applicable for when the frontend calls an API in the `/app/api` folder. ::: diff --git a/v2/emailpassword/nextjs/app-directory/setting-up-backend.mdx b/v2/emailpassword/nextjs/app-directory/setting-up-backend.mdx index b06c3ac05..c90c6f5e5 100644 --- a/v2/emailpassword/nextjs/app-directory/setting-up-backend.mdx +++ b/v2/emailpassword/nextjs/app-directory/setting-up-backend.mdx @@ -11,6 +11,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import AppInfoForm from "/src/components/appInfoForm" + # 3. Adding auth APIs We will add all the backend APIs for auth on `/api/auth`. This can be changed by setting the `apiBasePath` property in the `appInfo` object in the `appInfo.ts` file. For the rest of this page, we will assume you are using `/api/auth`. @@ -37,7 +38,7 @@ import { ensureSuperTokensInit } from '../../../config/backend'; ensureSuperTokensInit(); -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); export async function GET(request: NextRequest) { const res = await handleCall(request); diff --git a/v2/emailpassword/nextjs/protecting-route.mdx b/v2/emailpassword/nextjs/protecting-route.mdx index 21505368e..6de7c2164 100644 --- a/v2/emailpassword/nextjs/protecting-route.mdx +++ b/v2/emailpassword/nextjs/protecting-route.mdx @@ -9,9 +9,12 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import { OAuthFrontendVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # 4. Protecting a website route + + diff --git a/v2/emailpassword/nextjs/session-verification/in-api.mdx b/v2/emailpassword/nextjs/session-verification/in-api.mdx index e0d57c781..182b4d5d2 100644 --- a/v2/emailpassword/nextjs/session-verification/in-api.mdx +++ b/v2/emailpassword/nextjs/session-verification/in-api.mdx @@ -8,9 +8,13 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # 5a. Session verification in an API call + + :::note This is applicable for when the frontend calls an API in the `/pages/api` folder. ::: diff --git a/v2/emailpassword/nextjs/session-verification/in-ssr.mdx b/v2/emailpassword/nextjs/session-verification/in-ssr.mdx index fcdfb1c8d..4ed75df27 100644 --- a/v2/emailpassword/nextjs/session-verification/in-ssr.mdx +++ b/v2/emailpassword/nextjs/session-verification/in-ssr.mdx @@ -10,9 +10,12 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import CoreInjector from "/src/components/coreInjector" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 5b. Session verification in getServerSideProps + + :::note This is applicable for when verifying a session in `getServerSideProps` or `getInitialProps`. ::: diff --git a/v2/emailpassword/pre-built-ui/enable-email-verification.mdx b/v2/emailpassword/pre-built-ui/enable-email-verification.mdx index 75f90ad35..401e208f8 100644 --- a/v2/emailpassword/pre-built-ui/enable-email-verification.mdx +++ b/v2/emailpassword/pre-built-ui/enable-email-verification.mdx @@ -15,6 +15,7 @@ import {Answer} from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; import TabItem from "@theme/TabItem"; +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; @@ -24,6 +25,8 @@ There are two modes of email verification: - `REQUIRED`: Requires that the user's email is verified before they can access your application's frontend or backend routes (that are protected with a session). - `OPTIONAL`: Adds information about email verification into the session, but leaves it up to you to enforce it on the backend and frontend based on your business logic. + + ## Step 1: Backend setup diff --git a/v2/emailpassword/pre-built-ui/further-reading/email-verification.mdx b/v2/emailpassword/pre-built-ui/further-reading/email-verification.mdx index 6ceea4ed2..e05b3ee5a 100644 --- a/v2/emailpassword/pre-built-ui/further-reading/email-verification.mdx +++ b/v2/emailpassword/pre-built-ui/further-reading/email-verification.mdx @@ -7,6 +7,8 @@ hide_title: true +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" + # Email verification There are two modes of email verification: @@ -15,6 +17,8 @@ There are two modes of email verification: ## Information stored in session + + On sign in or sign up, SuperTokens adds information about the email verification status in the session's payload using the `EmailVerificationClaim`. It looks like this: ```json diff --git a/v2/emailpassword/pre-built-ui/handling-session-tokens.mdx b/v2/emailpassword/pre-built-ui/handling-session-tokens.mdx index 05314c1d7..69b80421f 100644 --- a/v2/emailpassword/pre-built-ui/handling-session-tokens.mdx +++ b/v2/emailpassword/pre-built-ui/handling-session-tokens.mdx @@ -10,6 +10,7 @@ hide_title: true import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; @@ -27,9 +28,9 @@ Our frontend SDK handles everything for you. You only need to make sure that you Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. -:::note By default, our web SDKs use cookies to provide credentials. -::: + + diff --git a/v2/emailpassword/pre-built-ui/securing-routes.mdx b/v2/emailpassword/pre-built-ui/securing-routes.mdx index 22d56941e..98c6911b0 100644 --- a/v2/emailpassword/pre-built-ui/securing-routes.mdx +++ b/v2/emailpassword/pre-built-ui/securing-routes.mdx @@ -14,6 +14,7 @@ import TabItem from '@theme/TabItem'; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import {Question, Answer}from "/src/components/question" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" # Securing your API and frontend routes @@ -24,6 +25,8 @@ import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" + + ### Requiring an active session For your APIs that require a user to be logged in, use the `verifySession` middleware @@ -436,7 +439,7 @@ In case of successful session verification, you get access to a `session` object ### Microservice authentication -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/introduction). +For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/unified-login/machine-to-machine-authentication). @@ -446,6 +449,14 @@ For authentication between microservices on your backend, checkout the [microser +:::caution + +These instructions only apply to scenarios in which you are using **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction) with **OAuth2 Access Tokens**, please check the [specific use case page](/docs/unified-login/introduction#when-to-use-unified-login) for relevant information. + +::: + diff --git a/v2/emailpassword/serverless/with-aws-lambda/authorizer.mdx b/v2/emailpassword/serverless/with-aws-lambda/authorizer.mdx index 078cb159b..5f3688425 100644 --- a/v2/emailpassword/serverless/with-aws-lambda/authorizer.mdx +++ b/v2/emailpassword/serverless/with-aws-lambda/authorizer.mdx @@ -10,9 +10,12 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Using Lambda Authorizers + + You can use a lambda as an Authorizer in API Gateways. This will enable you to use SuperTokens in a lambda to authorize requests to other integrations (e.g., AppSync). An Authorizer pointed to this lambda will add `context.authorizer.principalId` that you can map to a header. For example, you can map this to an "x-user-id" header which will be set to the id of the logged-in user. If there is no valid session for the request, this header won't exist. ## 1) Add configurations and dependencies diff --git a/v2/emailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/emailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx index 86055a1d9..14f128558 100644 --- a/v2/emailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/emailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -16,7 +16,14 @@ import TabItem from '@theme/TabItem'; # Using JWT Authorizers :::caution + AWS supports JWT authorizers for HTTP APIs and not REST APIs on the API Gateway service. For REST APIs follow the [Lambda authorizer](./authorizer) guide + +This guide will work if you are using **SuperTokens Session Tokens**. + +If you implementing an **OAuth2** setup, through the [**Unified Login**](/docs/unified-login/introduction) or the [**Microservice Authentication**](/docs/microservice_auth/client-credentials) features, you will have to manually set the token audience property. +Please check the referenced pages for more information. + ::: ## 1) Add the `aud` claim in the JWT based on the authorizer configuration diff --git a/v2/emailpassword/serverless/with-aws-lambda/session-verification.mdx b/v2/emailpassword/serverless/with-aws-lambda/session-verification.mdx index 02857b5e7..ae4954e93 100644 --- a/v2/emailpassword/serverless/with-aws-lambda/session-verification.mdx +++ b/v2/emailpassword/serverless/with-aws-lambda/session-verification.mdx @@ -9,9 +9,12 @@ hide_title: true import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 3. Session verification / Building your APIs + + When building your own APIs, you may need to verify the session of the user before proceeding further. SuperTokens SDK exposes a `verifySession` function that can be utilized for this. In this guide, we will be creating a `/user` `GET` route that will return the current session information. ## 1) Add `/user` `GET` route in your API Gateway diff --git a/v2/emailpassword/serverless/with-netlify/session-verification.mdx b/v2/emailpassword/serverless/with-netlify/session-verification.mdx index 1fd4b1a1f..b28b7cd62 100644 --- a/v2/emailpassword/serverless/with-netlify/session-verification.mdx +++ b/v2/emailpassword/serverless/with-netlify/session-verification.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # 4. Session verification / Building your APIs + + For this guide, we will assume that we want an API `/.netlify/functions/user GET` which returns the current session information. ## 1) Create a new file `netlify/functions/user.js` diff --git a/v2/emailpassword/user-roles/protecting-routes.mdx b/v2/emailpassword/user-roles/protecting-routes.mdx index 9d497d1ca..a970a8ab3 100644 --- a/v2/emailpassword/user-roles/protecting-routes.mdx +++ b/v2/emailpassword/user-roles/protecting-routes.mdx @@ -19,9 +19,19 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting API and frontend routes +:::caution + +This guide applies to scenarios which involve **SuperTokens Session Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), that uses **OAuth2 Access Tokens**, please check our [separate page](/docs/unified-login/customizations/verify-tokens) that shows you how to validate them. +You will have to check for the `roles` claim in the token payload. + +::: + ## Protecting API routes In your API routes you: diff --git a/v2/mfa/protect-routes.mdx b/v2/mfa/protect-routes.mdx index d113dad77..32038de9f 100644 --- a/v2/mfa/protect-routes.mdx +++ b/v2/mfa/protect-routes.mdx @@ -23,6 +23,15 @@ import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs" # Protecting frontend and backend routes +:::caution + +This guide only applies to scenarios which involve **SuperTokens Session Tokens**. + +If you are implementing either, [**Unified Login**](/docs/unified-login/introduction) or [**Microservice Authentication**](/docs/microservice_auth/introduction), features that make use of **OAuth2 Access Tokens**, please check the [separate page](/docs/unified-login/customizations/verify-tokens) that shows you how to verify those types of tokens. + +One thing to note here is that, with **OAuth2 Access Tokens**, you don't need to check the MFA claims. You will get the token once the MFA flow is done. +::: + In thie section, we will talk about how to protect your frontend and backend routes to make them accessible only when the user has finished all the MFA challenges configured for them. In both the backend and the frontend, we will protect routes based on the value of [MFA claim in the session's access token payload](./important-concepts#how-are-auth-factors-marked-as-completed). diff --git a/v2/mfa/step-up-auth.mdx b/v2/mfa/step-up-auth.mdx index 1e72571d7..a2db23002 100644 --- a/v2/mfa/step-up-auth.mdx +++ b/v2/mfa/step-up-auth.mdx @@ -31,6 +31,12 @@ SuperTokens allows you to implement step up auth using the following factors: You can implement these as full page navigations, or as popups on the current page. +:::caution + +If you are using **OAuth2** in your configuration, step up authentication is not supported at the moment. + +::: + ## Step 1) Adding backend validators To protect sensitive APIs with step up auth, you need to check that the user has completed the required auth challenge within a certain amount of time. If they haven't, you should return a `403` to the frontend which highlights which factor is required. The frontend can then consume this and show the auth challenge to the user. diff --git a/v2/microservice_auth/client-credentials.mdx b/v2/microservice_auth/client-credentials.mdx new file mode 100644 index 000000000..3f403b0f3 --- /dev/null +++ b/v2/microservice_auth/client-credentials.mdx @@ -0,0 +1,655 @@ +--- +title: Client Credentials Flow +hide_title: true +--- + +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" +import OAuthFrontendTabs from "/src/components/tabs/OAuthFrontendTabs" +import OAuthBackendTabs from "/src/components/tabs/OAuthBackendTabs" +import CoreInjector from "/src/components/coreInjector" +import AppInfoForm from "/src/components/appInfoForm" +import TabItem from '@theme/TabItem'; +import OAuthPaidBanner from '../community/reusableMD/oauth-paid-banner.mdx' + + + + +# Client Credentials Authentication + + +:::info +This page makes use of various **OAuth2** terminology. If you need a refresher on what everything means, please check our [separate page](/docs/unified-login/introduction#oauth2-basics) that explains most of the concepts. +::: + + +Before going into the actual steps let's start by imagining a real life example that we can reference along the way. +This will make it easier to understand what we are doing. + +We are going to configure authentication for the following setup: +- A **Calendar Service** that exposes these actions: `event.view`, `event.create`, `event.update` and `event.delete` +- A **File Service** that exposes these actions: `file.view`, `file.create`, `file.update` and `file.delete` +- A **Task Service** that interacts with the **Calendar Service** and the **File Service** in the process of scheduling a task + +Our aim will be to allow the **Task Service** to perform an authenticated action on the **Calendar Service**. +Now let's get into the actual steps. + + + + + + +## 1. Enable the OAuth2 features from the Dashboard + +You will first have to enable the **OAuth2** features from the **SuperTokens.com Dashboard**. +1. Open the **SuperTokens.com Dashboard** +2. Click on the **Enabled Paid Features** button +3. Click on **Managed Service** +4. Check the **OAuth 2.0** option +5. Click *Save* + +Now you should be able to use the OAuth2 recipes in your applications. + + + +## 2. Create the OAuth2 Clients + + + + + +For each of your **`microservices`** you will have to create a separate [**OAuth2 client**](/docs/unified-login/introduction#client). +This can be done by directly calling the **SuperTokens Core** API. + +```bash +# You will have to run this for each one of your applications +# Adjust the attributes based on that +curl -X POST ^{coreInjector_uri_without_quotes}/recipe/oauth2/admin/clients \ + -H "Content-Type: application/json" \ + -H "api-key: ^{coreInjector_api_key_without_quotes}" \ + -d '{ + "clientName": "", + "grantTypes": ["client_credentials"], + "scope": " ", + "audience": [""] + }' +``` + + +- `clientName` - Then name of the client that will be used later for identification. +- `grantTypes` - The grant types that the [**Client**](/docs/unified-login/introduction#client) will use. + - `clientCredentials`: Allows the client to directly request an Access Token by authenticating itself with the Authorization Server using its own client credentials. +- `audience` - Value used to identify for whom a token was issued. The created client will be able to generate access token only for the specified audiences. +- `scope` - A space separated string of scopes that the [**Client**](/docs/unified-login/introduction#client) will request access to. + + +:::info Custom Example + +To create a client for our **Task Service** we will have to use the following attributes: + +```json + { + "clientName": "Task Service", + "grantTypes": ["client_credentials"], + "scope": "event.view event.create event.edit event.delete file.view file.create file.edit file.delete", + "audience": ["event", "file"] + } +``` + +This will allow the **Task Service** to perform all types of actions against both of the other services as long as it has a valid **OAuth2 Access Token**. + +::: + +If the creation was successful, the API will return a response that looks like this: + +```json +{ + "clientName": "", + "clientId": "", + "clientSecret": "", + "callbackUrls": [], +} + +``` + +### Change the default token lifespan + +By default, the token used in the authorization flow will have a 1 hour lifespan. + +We recommend that you change it in order to use short lived tokens for improved security. +To do this you will need to set the `clientCredentialsGrantAccessTokenLifespan` property in the [**Client**](/docs/unified-login/introduction#client) creation request body. + +Use string values that signify time duration in milliecoseconds, seconds, minutes or hours (e.g. `"2000ms"`, `"60s"`, `"30m"`, `"1h"`). + +## 3. Set Up your Authorization Service + +In your [**Authorization Service**](/docs/unified-login/introduction#authorization-service) backend you will need to initialize the **OAuth2Provider** recipe. + + + + + +Update the `supertokens.init` call to include the new recipe. + +```tsx + +import supertokens from "supertokens-node"; +import OAuth2Provider from "supertokens-node/recipe/oauth2provider"; + + +supertokens.init({ + supertokens: { + connectionURI: "...", + apiKey: "...", + }, + appInfo: { + appName: "...", + apiDomain: "...", + websiteDomain: "...", + }, + recipeList: [ + OAuth2Provider.init(), + ] +}); + +``` + + + + + + +:::caution + + +At the moment we do not have support creating OAuth2 providers in the Go SDK. +You can use the [legacy method](/docs/microservice_auth/legacy/implementation-guide) in order to authenticate microservices based on your language. + +::: + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Python SDK. +You can use the [legacy method](/docs/microservice_auth/legacy/implementation-guide) in order to authenticate microservices based on your language. + +::: + + + + + + +## 4. Generate Access Tokens + + + +Now you can directly call the [**Authorization Server**](/docs/unified-login/introduction#authorization-server) to generate Access Tokens. +Check the following code snippet to see how you can do that: + + +```bash + +curl -X POST ^{form_apiDomain}^{form_apiBasePath}/oauth/token \ +-H "Content-Type: application/json" \ +-d '{ + "clientId": "", + "clientSecret": "", + "grantType": "client_credentials", + "scope": [""], + "audience": "" +}' + +``` + +You should limit the scopes that you are requesting to just the ones necessary to perform the desired action. + +:::info Custom Example + +If the **Task Service** wants to create an event on the **Calendar Service** we will have to generate a token with the following attributes: + +```json + { + "clientId": "", + "clientSecret": "", + "grantType": "client_credentials", + "scope": ["event.create"], + "audience": "event" + } +``` + +::: + +The **Authorization Server** will return a response that will look like this: + +```json +{ + "accessToken": "", + "expiresIn": 3600 +} +``` + +You will have to save the `accessToken` in memory so that you can use it in the next step. +The `expiresIn` field will tell you how long the token is valid for. + +Each service that you communicate with will need its own token. + +Now that you have an **OAuth2 Access Token** you can use it when communicating with the other services. +Just keep in mind to generate a new one when it expires. + +## 5. Verify an OAuth2 Access Token + +In order to check the validity of a token we recommend using a generic **JWT** verification library. + +Besides the standard **OAuth2** token claims our implementation includes an additional one called `stt`. +This stands for `SuperTokens Token Type`. +It is used to make sure that the validation is performed for the correct token type: +- `0` represents a **SuperTokens Session Access Token** +- `1` represents an **OAuth2 Access Token** +- `2` represents an **OAuth2 ID Token**. + + + + + +For NodeJS you can use [jose](https://github.com/panva/jose) to verify the token. + +```tsx +import jose from "jose"; + +const JWKS = jose.createRemoteJWKSet(new URL('^{form_apiDomain}^{form_apiBasePath}/jwt/jwks.json')) + +async function validateClientCredentialsToken(jwt: string) { + const requiredScope = ""; + const audience = ''; + + try { + const { payload } = await jose.jwtVerify(jwt, JWKS, { + audience, + requiredClaims: ['stt', 'scp'], + }); + + if(payload.stt !== 1) return false; + + const scopes = payload.scp as string[]; + return scopes.includes(requiredScope); + } catch (err) { + return false; + } +} + +``` + + + + + + +You can use the [jwx](https://github.com/lestrrat-go/jwx) library to verify the token. + +```go +import ( + "context" + "fmt" + + "github.com/lestrrat-go/jwx/jwt" + "github.com/lestrrat-go/jwx/jwk" +) + +func ValidateToken(token string) bool { + apiDomain := "^{form_apiDomain}" + apiBasePath := "^{form_apiBasePath}" + requiredScope := "" + + jwksURL := fmt.Sprintf("%s%sjwt/jwks.json", apiDomain, apiBasePath) + jwks, err := jwk.Fetch(context.Background(), jwksURL) + if err != nil { + return false + } + + parsedToken, err := jwt.Parse( + []byte(token), + jwt.WithKeySet(jwks), + jwt.WithClaimValue("stt", 1), + jwt.WithAudience(""), + ) + if err != nil { + return false + } + + scp, ok := parsedToken.Get("scp") + if !ok { + return false + } + + scopes, ok := scp.([]interface{}) + if !ok { + return false + } + + for _, scope := range scopes { + if scope, ok := scope.(string); ok && scope == requiredScope { + return true + } + } + + return false; +} +``` + + + + + +You can use the [PyJWT](https://github.com/jpadilla/pyjwt) library to verify the token. + +```python +import requests +import jwt +from jwt import PyJWKClient + +def validate_token(token): + api_domain = "^{form_apiDomain}" + api_base_path = "^{form_apiBasePath}" + audience = "" + required_scope = "" + + jwks_url = f"{api_domain}{api_base_path}jwt/jwks.json" + jwks_client = PyJWKClient(jwks_url) + + try: + signing_key = jwks_client.get_signing_keys_from_jwt(token) + decoded = jwt.decode( + token, + signing_key.key, + algorithms=['RS256'], + audience=audience, + options={"require": ["stt", "scp"]} + ) + + sst = decoded.get('sst', None) + if stt != 1: + return False + + + scopes = decoded.get('scp', []) + if required_scope not in scopes: + return False + + return True + except Exception as e: + return False + +``` + + + + + +You can use the [Firebase JWT](https://github.com/firebase/php-jwt) library to verify the token. + +```php + +require 'vendor/autoload.php'; + +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +function validateToken($jwt) { + $apiDomain = "^{form_apiDomain}"; + $apiBasePath = "^{form_apiBasePath}"; + $jwksUrl = $apiDomain . $apiBasePath . '/jwt/jwks.json'; + $requiredScope = ""; + $audience = ""; + + $jwks = json_decode(file_get_contents($jwksUrl), true); + try { + $decoded = JWT::decode($jwt, JWK::parseKeySet($jwks), 'RS256')); + if ($decoded->aud !== $audience) { + return false; + } + if ($decoded->sst !== 1) { + return false; + } + return in_array($requiredScope, $decoded->scp); + } catch (Exception $e) { + return false; + } +} + +``` + + + + + +You can use the [Auth0 JWT](https://github.com/auth0/java-jwt) library to verify the token. + +```java + +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; +import com.auth0.jwt.JWTVerifier.Base; +import com.auth0.jwt.algorithms.Algorithm; + +import java.net.URL; +import java.util.Map; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.nio.charset.StandardCharsets; +import java.util.Scanner; + +public class JWTVerifier { + + private static final String JWKS_URL = "^{form_apiDomain}^{form_apiBasePath}jwt/jwks.json"; + private static final String AUDIENCE = ""; + + private static Map fetchJWKS() throws Exception { + URL url = new URL(JWKS_URL); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + + InputStream responseStream = connection.getInputStream(); + Scanner scanner = new Scanner(responseStream, StandardCharsets.UTF_8.name()); + String responseBody = scanner.useDelimiter("\\A").next(); + scanner.close(); + + return JWT.decode(responseBody).getHeader(); + } + + public static boolean validateToken(String token) { + try { + Map jwks = fetchJWKS(); + + Algorithm algorithm = Algorithm.RSA256(jwks.get("x5c"), null); + JWTVerifier verifier = JWT.require(algorithm) + .withAudience(AUDIENCE) + .build(); + + DecodedJWT jwt = verifier.verify(token); + if(jwt.getClaim("sst").asInt() != 1) { + return false; + } + List scopes = jwt.getClaim("scp").asList(); + return scopes.contains(requiredScope); + } catch (Exception e) { + return false; + } + } +} +``` + + + + + +You can use the [IdentityModel](https://github.com/IdentityModel/IdentityModel) library to verify the token. + +```csharp +using System; +using System.Linq; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +class ClientCredentialsTokenValidator +{ + static async Task ValidateToken(string jwtStr) + { + string apiDomain = "^{form_apiDomain}"; + string apiBasePath = "^{form_apiBasePath}"; + string audience = ""; + string requiredScope = ""; + + HttpClient client = new HttpClient(); + var response = await client.GetStringAsync($"{apiDomain}{apiBasePath}jwt/jwks.json"); + var jwks = new JsonWebKeySet(response); + + var tokenHandler = new JwtSecurityTokenHandler(); + var validationParameters = new TokenValidationParameters + { + ValidAudience = audience, + IssuerSigningKeys = jwks.Keys + }; + + try + { + SecurityToken validatedToken; + var principal = tokenHandler.ValidateToken(jwtStr, validationParameters, out validatedToken); + var claims = principal.Claims.ToDictionary(c => c.Type, c => c.Value); + + if (!claims.ContainsKey("stt") || claims["stt"] != "1") + { + return false; + } + + var scopes = claims["scp"].Split(" "); + if (!scopes.Contains(requiredScope)) + { + return false; + } + + return true; + } + catch (Exception) + { + return false; + } + } +} + +``` + + + + + + + +:::info Custom Example + +If the **Task Service** uses the previously generated token to create a calendar event, the **Calendar Service** will have to check the following: +- The `stt` claim should be set to `1` +- The `scp` claim contains `event.create` +- The `aud` calim should be set to `event` + +::: + +### Handle Both SuperTokens Session Tokens and OAuth2 Access Tokens + +If you are using your **Authorization Service** also as a **Resource Server** you will have to account for this in the way you verify the sessions. + +This is needed because we are using two types of tokens: +- **SuperTokens Session Access Token**: Used during the login/logout flows. +- **OAuth2 Access Token**: Used to access protected resources and perform actions that need authorization. + +Hence we need a way to distinguish between these two and prevent errors. + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import express, { Request, Response, NextFunction } from 'express'; +import jose from "jose"; + +async function verifySession(req: Request, res: Response, next: NextFunction) { + let session = undefined; + try { + session = await Session.getSession(req, res, { sessionRequired: false }); + } catch (err) { + if ( + !Session.Error.isErrorFromSuperTokens(err) || + err.type !== Session.Error.TRY_REFRESH_TOKEN + ) { + return next(err); + } + } + + // In this case we are dealing with a SuperTokens Session that has been validated + if (session !== undefined) { + return next(); + } + + // The OAuth2 Access Token needs to be manually extracted and validated + let jwt: string | undefined = undefined; + if (req.headers["authorization"]) { + jwt = req.headers["authorization"].split("Bearer ")[1]; + } + if (jwt === undefined) { + return next(new Error("No JWT found in the request")); + } + + try { + await validateToken(jwt); + return next(); + } catch (err) { + return next(err); + } +} + +const JWKS = jose.createRemoteJWKSet( + new URL("^{form_apiDomain}^{form_apiBasePath}jwt/jwks.json"), +); + +// This is a basic example on how to validate an OAuth2 Token +// Use the previous example to extend it +async function validateToken(jwt: string) { + const { payload } = await jose.jwtVerify(jwt, JWKS, { + requiredClaims: ["stt", "scp", "sub"], + }); + + if (payload.stt !== 1) throw new Error("Invalid token"); + + + // If the Authorizaton Server will handle different types of Authorization Flows + // You can differentiate between the different types of tokens by checking the `sessionHandle` claim + const sessionHandle = payload['sessionHandle'] as string | undefined; + if(sessionHandle === undefined) { + // We are dealing with a Client Credentials Token + // You can perform microservice authentication checks here + } else { + // Here we are validating tokens that have been generated in the Authorization Code Flow + } +} + + +// You can then use the function as a middleware for a protected route +const app = express(); +app.get("/protected", verifySession, async (req, res) => { + // Custom logic +}); + +``` + + + + + diff --git a/v2/microservice_auth/introduction.mdx b/v2/microservice_auth/introduction.mdx index bae97f0ab..9dfe6f8fe 100644 --- a/v2/microservice_auth/introduction.mdx +++ b/v2/microservice_auth/introduction.mdx @@ -6,10 +6,32 @@ hide_title: true # Introduction -This guide explains how you can achieve auth between microservices using SuperTokens. When microservice `M1` wants to talk to microservice `M2`, `M1` will start by creating a JWT (by contacting the SuperTokens core), and pass that JWT to `M2`. +This guide explains how you can authenticate your microservices with **SuperTokens**. -`M2` will then verify the JWT, check that it's created by another microservice, and only then proceed with serving the request. +The standard way of doing this is to create an **OAuth2 Provider** and use the **OAuth2 Client Credentials Flow** for authorization. +At the moment this is only supported by the **NodeJS** SDK. +If you are using **python** or **golang** you will have to implement the [legacy flow](/docs/microservice_auth/legacy/implementation-guide). -Microservice auth flow diagram +## Authentication Steps + +:::caution + +This is only supported by the **NodeJS** SDK. + +::: + +This flow uses common **OAuth2** terminology. If you want a short explanation on how **OAuth2** works and its concepts, please check out [this page](/docs/unified-login/introduction#oauth2-basics). + +Machine to Machine Authentication + + +In the **Client Credentials Flow** the authentication sequence will work in the following way: + +1. `Service A` uses credentials to get an **OAuth2 Access Token** +2. [**Authorization Service**](/docs/unified-login/introduction#authorization-service) returns the **OAuth2 Access Token** +3. `Service A` uses the **OAuth2 Access Token** to communicate with `Service B` +4. `Service B` validates the **OAuth2 Access Token** +5. If the token is valid `Service B` returns the requested resource + +Check our [**extensive guide**](/docs/microservice_auth/client-credentials) that will show you how to setup the **Authorization Service** and how to complete all the steps using **SuperTokens**. -In the next sections, we will be showing you how to create and verify a JWT using SuperTokens. \ No newline at end of file diff --git a/v2/microservice_auth/jwt-creation.mdx b/v2/microservice_auth/jwt-creation.mdx index 3c8b1468e..17df93788 100644 --- a/v2/microservice_auth/jwt-creation.mdx +++ b/v2/microservice_auth/jwt-creation.mdx @@ -4,267 +4,6 @@ title: Creating a JWT hide_title: true --- -import WithWithoutSDK from "/src/components/tabs/WithWithoutSDK"; -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; -import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs" +import Redirector from '/src/components/Redirector'; -# Creating a JWT - -## When to create a JWT? -The first step is to create a JWT from the microservice that will be sending the request (let's call this microservice `M1`). This JWT will be verified by other microservices when `M1` sends them a request. Since this JWT remains static per microservice, the best time to create this is on process starts - i.e. when `M1` starts. - -## What to add in the JWT? -The JWT can contain any information you like, but at a minimum, it needs to contain information proving that it is a microservice that is allowed to query other microservices in your infrastructure. This is needed since you may issue a JWT to an end user as well, and they should not be able to query any microservice directly. - -We can add the following claim in the JWT to "mark" the JWT as one that is meant for microservice auth only: -```json -{..., "source": "microservice", ...} -``` - -In the receiving microservice (`M2`), we can then [verify the JWT](./jwt-verification/index) and check that this claim is present before serving the request. - - -## Code for creating a JWT - - - - -First, we need to initialise the `JWT` recipe in the `supertokens.init` function call: - - - - -```tsx -import supertokens from "supertokens-node" -import jwt from "supertokens-node/recipe/jwt" - -supertokens.init({ - appInfo: { - apiDomain: "...", - appName: "...", - websiteDomain: "..." - }, - supertokens: { - connectionURI: "...", // location of the core - apiKey: "..." // provide the core's API key if configured - }, - recipeList: [ - // highlight-next-line - jwt.init() - ] -}) -``` - - - - -```go -import ( - "github.com/supertokens/supertokens-golang/recipe/jwt" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func main() { - supertokens.Init(supertokens.TypeInput{ - AppInfo: supertokens.AppInfo{ - AppName: "...", - WebsiteDomain: "...", - APIDomain: "...", - }, - Supertokens: &supertokens.ConnectionInfo{ - ConnectionURI: "...", // location of the core - APIKey: "...", // provide the core's API key if configured - }, - RecipeList: []supertokens.Recipe{ - // highlight-next-line - jwt.Init(nil), - }, - }) -} -``` - - - - -```python -from supertokens_python import init, InputAppInfo, SupertokensConfig -from supertokens_python.recipe import jwt - -init( - app_info=InputAppInfo( - app_name="...", - api_domain="...", - website_domain="...", - ), - supertokens_config=SupertokensConfig( - connection_uri="...", # location of the core - api_key="..." # provide the core's API key if configured - ), - framework='django', - recipe_list=[ - # highlight-next-line - jwt.init(), - ], -) -``` - - - - -:::important -- The value of `apiDomain` should be the domain part of the JWKS URL that will be used to verify the JWT (from `M2`). This should ideally be the domain of the microservice that has all the other SuperTokens' recipes initialised in them. -- If this microservice does not initialise any other recipe, the values of `appName` and `websiteDomain` don't matter. -::: - -After this, you can use the JWT recipe to create your own JWT whenever required: - - - - -```tsx -import jwt from "supertokens-node/recipe/jwt" - -async function createJWT(payload: any) { - let jwtResponse = await jwt.createJWT({ - ...payload, - source: "microservice" - }); - if (jwtResponse.status === "OK") { - // Send JWT as Authorization header to M2 - return jwtResponse.jwt; - } - throw new Error("Unable to create JWT. Should never come here.") -} -``` - - - - -```go -import ( - "fmt" - - "github.com/supertokens/supertokens-golang/recipe/jwt" -) - -func main() { - jwtResponse, err := jwt.CreateJWT(map[string]interface{}{ - "source": "microservice", - // ...additional payload - }, nil, nil) - if err != nil { - // handle error - } - jwtString := jwtResponse.OK.Jwt - fmt.Println(jwtString) - // Send JWT as Authorization header to M2 -} -``` - - - - - - - -```python -from supertokens_python.recipe.jwt import asyncio -from supertokens_python.recipe.jwt.interfaces import CreateJwtOkResult - -async def create_jwt(): - jwtResponse = await asyncio.create_jwt({ - "source": "microservice", - # ... extra payload - }) - - if isinstance(jwtResponse, CreateJwtOkResult): - _ = jwtResponse.jwt - # Send JWT as Authorization header to M2 - else: - raise Exception("Unable to create JWT. Should never come here.") -``` - - - - -```python -from supertokens_python.recipe.jwt.syncio import create_jwt -from supertokens_python.recipe.jwt.interfaces import CreateJwtOkResult - -jwtResponse = create_jwt({ - "source": "microservice", - # ... extra payload -}) - -if isinstance(jwtResponse, CreateJwtOkResult): - jwtStr = jwtResponse.jwt - # Send JWT as Authorization header to M2 -else: - raise Exception("Unable to create JWT. Should never come here.") -``` - - - - - - - -:::note -By default, the lifetime of the JWT will be a 100 years. You can pass a second argument to the `createJWT` function indicating a custom lifetime (in seconds) for the JWT. -::: - - -:::note -By default, the JWT is signed by a static key, not subject to the key rotation normally applied to access tokens. You can pass `false` as the third argument to the `createJWT` function to use use the dynamic keys. -::: - - - - -You can send a `HTTP` request to the core as follows: - -```bash -curl --location --request POST '${connectionURI}/recipe/jwt' \ ---header 'rid: jwt' \ ---header 'api-key: ${APIKey}' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "payload": { - "source": "microservice", - ... - }, - "useStaticSigningKey": true, - "algorithm": "RS256", - "jwksDomain": "${apiDomain}", - "validity": ${validityInSeconds} -}' -``` -- The value of `${connectionURI}` is the core's location -- `${APIKey}` is the API key to query the core. This is only needed if an API key is configured on the core. -- The value of `${apiDomain}` is the domain on which the JWKs URLs will be served from -- `${validityInSeconds}` is the lifetime of the JWT - -An example response is as follows: -```json -{ - "status": "OK", - "jwt": "eyJraWQiOiI0YTE...rCFPcIRgzu_bChIIpFdA" -} - -``` - - - - -## What to do with the JWT? -Once the JWT is created, you can store it in a (globally accessible) variable and access it when you want to talk to a microservice. You can add the JWT as an `Authorization: Bearer` token like so: - -```bash -curl --location --request POST 'https://microservice_location/path' \ ---header 'Authorization: Bearer eyJraWQiOiI0YTE...rCFPcIRgzu_bChIIpFdA' \ ---header 'Content-Type: application/json; charset=utf-8' \ ---data-raw '{ - "request": "payload" -}' -``` + diff --git a/v2/microservice_auth/jwt-verification/get-public-key.mdx b/v2/microservice_auth/jwt-verification/get-public-key.mdx index 8bac5d330..6576bac47 100644 --- a/v2/microservice_auth/jwt-verification/get-public-key.mdx +++ b/v2/microservice_auth/jwt-verification/get-public-key.mdx @@ -6,4 +6,5 @@ hide_title: true import Redirector from '/src/components/Redirector'; - \ No newline at end of file + + diff --git a/v2/microservice_auth/jwt-verification/index.mdx b/v2/microservice_auth/jwt-verification/index.mdx index 00f77bdc2..9bcaed28d 100644 --- a/v2/microservice_auth/jwt-verification/index.mdx +++ b/v2/microservice_auth/jwt-verification/index.mdx @@ -4,379 +4,7 @@ title: Verify JWTs hide_title: true --- -import AppInfoForm from "/src/components/appInfoForm" -import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; -import TabItem from '@theme/TabItem'; +import Redirector from '/src/components/Redirector'; -# Verify JWTs + - - -When a target microservice receives a JWT, it must first verify it before proceeding to serve the request. There are two steps here: -- A standard verification of the JWT -- Checking the JWT claim to make sure that another microservice has queried it. - -## Standard verification of a JWT - -### Method 1) Using JWKS endpoint - - - - -#### a) Get JWKS endpoint - -The JWKS endpoint is `{apiDomain}/{apiBasePath}/jwt/jwks.json`. Here the `apiDomain` and `apiBasePath` are values pointing to the server in which you have initalised SuperTokens using our backend SDK. - -#### b) Verify the JWT - -Some libraries let you provide a JWKS endpoint to verify a JWT. For example for NodeJS you can use `jsonwebtoken` and `jwks-rsa` together to achieve this. - -```ts -import JsonWebToken, { JwtHeader, SigningKeyCallback } from 'jsonwebtoken'; -import jwksClient from 'jwks-rsa'; - -var client = jwksClient({ - jwksUri: '^{form_apiDomain}^{form_apiBasePath}/jwt/jwks.json' -}); - -function getKey(header: JwtHeader, callback: SigningKeyCallback) { - client.getSigningKey(header.kid, function (err, key) { - var signingKey = key!.getPublicKey(); - callback(err, signingKey); - }); -} - -let jwt = "..."; -JsonWebToken.verify(jwt, getKey, {}, function (err, decoded) { - let decodedJWT = decoded; - // Use JWT -}); -``` - - - - -Refer to this [Github gist](https://gist.github.com/rishabhpoddar/ea31502923ec9a53136371f2b6317ffa) for a code reference of how use `PyJWK` to do JWT verification. The gist contains two files: -- `jwt_verification.py` (which you can just copy / paste into your application). You will have to modify the `JWKS_URI` in this file to point to your supertokens core instance (replacing the `try.supertokens.com` part of the URL). This file is written for `sync` python apps, and can be modified to work with `async` apps as well. - - This file essentially exposes a function called `verify_jwt` which takes an input JWT string. - - This function takes care of caching public keys in memory + auto refetching if the public keys have changed (which happens automatically every 24 hours with supertokens). This will not cause any user logouts, and is just a security feature. -- `views.py`: This is an example `GET` API which extracts the JWT token from the authorization header in the request and calls the `verify_jwt` function from the other file. - - - - -Refer to this [Github gist](https://gist.github.com/rishabhpoddar/8c26ed237add1a5b86481e72032abf8d) for a code reference of how use the Golang `jwt` lib to do session verification. The gist contains two files: -- `verifyToken.go` (which you can just copy / paste into your application). You will have to modify the `coreUrl` in this file to point to your supertokens core instance (replacing the `try.supertokens.com` part of the URL). - - This file essentially exposes a function called `GetJWKS` which returns a reference to the JWKS public keys that can be used for JWT verification. - - This function takes care of caching public keys in memory + auto refetching if the public keys have changed (which happens automatically every 24 hours with supertokens). This will not cause any user logouts, and is just a security feature. -- `main.go`: This is an example of how to verify a JWT using the golang JWT verification lib along with our helper function to get the JWKs keys. - - - - -### Method 2) Using public key string - - - - -Some JWT verification libraries require you to provide the JWT secret / public key for verification. You can obtain the JWT secret from SuperTokens in the following way: - -- First, we query the `JWKS.json` endpoint: - ```bash - curl --location --request GET '^{form_apiDomain}^{form_apiBasePath}/jwt/jwks.json' - - { - "keys": [ - { - "kty": "RSA", - "kid": "s-2de612a5-a5ba-413e-9216-4c43e2e78c86", - "n": "AMZruthvYz7Ft-Dp0BC_SEEJaWK91s_YA-RR81iLJ6BTT6gJp0CcV4DfBynFU_59dRGOZyVQpAW6Drnc_6LyZpVWHROzqt-Fjh8TAqodayhPJVuZt25eQiYrqcaK_dnuHrm8qwUq-hko6q1o1o9NIIZWNfUBEVWmNhyAJFk5bi3pLwtKPYrUQzVLcTdDUe4SIltvvfpYHbVFnYtxkBVmqO68j7sI8ktmTXM_heals-W6WmozabDkC9_ITCeRat2f7A2l0t4QzO0ZCzZcJfhusF4X1niKgY6yYXpbX6is4HCfhYfdabcE52xYMNl-gw9XDjsIxfBMUDvOFRHWlx0rU8c=", - "e": "AQAB", - "alg": "RS256", - "use": "sig" - }, - { - "kty": "RSA", - "kid": "d-230...802340", - "n": "AMZruthvYz7...lx0rU8c=", - "e": "...", - "alg": "RS256", - "use": "sig" - } - ] - } - ``` - - :::important - The above shows an example output which returns two keys. There could be more keys returned based on the configured key rotation setting in the core. If you notice, each key's `kid` starts with a `s-..` or a `d-..`. The `s-..` key is a static key that will never change, whereas `d-...` keys are dynamic keys that keep changing. So if you are hardcoding public keys somewhere, you always want to pick the `s-..` key. - ::: - -- Next, we run the NodeJS script below to convert the above output to a `PEM` file format. - ```tsx - import jwkToPem from 'jwk-to-pem'; - - // This JWK is copied from the result of the above SuperTokens core request - let jwk = { - "kty": "RSA", - "kid": "s-2de612a5-a5ba-413e-9216-4c43e2e78c86", - "n": "AMZruthvYz7Ft-Dp0BC_SEEJaWK91s_YA-RR81iLJ6BTT6gJp0CcV4DfBynFU_59dRGOZyVQpAW6Drnc_6LyZpVWHROzqt-Fjh8TAqodayhPJVuZt25eQiYrqcaK_dnuHrm8qwUq-hko6q1o1o9NIIZWNfUBEVWmNhyAJFk5bi3pLwtKPYrUQzVLcTdDUe4SIltvvfpYHbVFnYtxkBVmqO68j7sI8ktmTXM_heals-W6WmozabDkC9_ITCeRat2f7A2l0t4QzO0ZCzZcJfhusF4X1niKgY6yYXpbX6is4HCfhYfdabcE52xYMNl-gw9XDjsIxfBMUDvOFRHWlx0rU8c=", - "e": "AQAB", - "alg": "RS256", - "use": "sig" - }; - - // @ts-ignore - let certString = jwkToPem(jwk); - ``` - - The above snippet would generate the following certificate string: - - ```text - -----BEGIN PUBLIC KEY----- - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxmu62G9jPsW34OnQEL9I - QQlpYr3Wz9gD5FHzWIsnoFNPqAmnQJxXgN8HKcVT/n11EY5nJVCkBboOudz/ovJm - ... (truncated for display) - XhfWeIqBjrJheltfqKzgcJ+Fh91ptwTnbFgw2X6DD1cOOwjF8ExQO84VEdaXHStT - xwIDAQAB - -----END PUBLIC KEY----- - ``` - -- Now you can use the generated PEM string in your code like shown below: - ```ts - import JsonWebToken from 'jsonwebtoken'; - - // Truncated for display - let certificate = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki...\n-----END PUBLIC KEY-----"; - let jwt = "..."; // fetch the JWT from sAccessToken cookie or Authorization Bearer header - JsonWebToken.verify(jwt, certificate, function (err, decoded) { - let decodedJWT = decoded; - // Use JWT - }); - ``` - - - - -:::caution -Not applicable. Please use method 1 instead. -::: - - - - -:::caution -Not applicable. Please use method 1 instead. -::: - - - - -## Claim verification -The second step is to get the JWT payload and check that it has the `"source": "microservice"` claim: - - - - -```tsx -import JsonWebToken, { JwtHeader, SigningKeyCallback } from 'jsonwebtoken'; -import jwksClient from 'jwks-rsa'; - -var client = jwksClient({ - jwksUri: '^{form_apiDomain}^{form_apiBasePath}/jwt/jwks.json' -}); - -function getKey(header: JwtHeader, callback: SigningKeyCallback) { - client.getSigningKey(header.kid, function (err, key) { - var signingKey = key!.getPublicKey(); - callback(err, signingKey); - }); -} - -let jwt = "..."; -JsonWebToken.verify(jwt, getKey, {}, function (err, decoded) { - // highlight-start - let decodedJWT = decoded; - if (decodedJWT === undefined || typeof decodedJWT === "string" || decodedJWT.source === undefined || decodedJWT.source !== "microservice") { - // return a 401 unauthorised error - } else { - // handle API request... - } - // highlight-end -}); -``` - - - - -Referring once again to this [Github gist](https://gist.github.com/rishabhpoddar/ea31502923ec9a53136371f2b6317ffa), we can see in `views.py`, between lines 20 and 28, that we are checking for the value of a certain claim in the decoded JWT payload. You can do something similar and check for the `source` claim whose value must be `microservice`. If it is, then all's good, else you can return a 401. - - - - -Referring once again to this [Github gist](https://gist.github.com/rishabhpoddar/8c26ed237add1a5b86481e72032abf8d), we can see in `main.go`, between lines 32 and 44, that we are checking for the value of a certain claim in the decoded JWT payload. You can do something similar and check for the `source` claim whose value must be `microservice`. If it is, then all's good, else you can return a 401. - - - - -## M2M and frontend session verification for the same API -You may have a setup wherein the same API is called from the frontend as well as from other microservices. The frontend session works differently than m2m sessions, so we have to account for both forms of token inputs. - -Our approach here would be to first attempt frontend session verification, and if that fails, then attempt m2m jwt verification (using the above method). If both fails, then we send back a `401` response. - -We will use the [`getSession` function](https://supertokens.com/docs/session/common-customizations/sessions/session-verification-in-api/get-session) for frontend session verification. - - - - -```tsx -import express from "express"; -import Session from "supertokens-node/recipe/session"; -import JsonWebToken, { JwtHeader, SigningKeyCallback } from 'jsonwebtoken'; -import jwksClient from 'jwks-rsa'; - -let app = express(); - - -var client = jwksClient({ - jwksUri: '^{form_apiDomain}^{form_apiBasePath}/jwt/jwks.json' -}); - -function getKey(header: JwtHeader, callback: SigningKeyCallback) { - client.getSigningKey(header.kid, function (err, key) { - var signingKey = key!.getPublicKey(); - callback(err, signingKey); - }); -} - - -app.post("/like-comment", async (req, res, next) => { - // highlight-start - try { - let session = await Session.getSession(req, res, { sessionRequired: false }) - - if (session !== undefined) { - // API call from the frontend and session verification is successful.. - let userId = session.getUserId(); - } else { - // maybe this API is called from a microservice, so we attempt JWT verification as - // shown above. - let jwt = req.headers["authorization"]; - jwt = jwt === undefined ? undefined : jwt.split('Bearer ')[1]; - if (jwt === undefined) { - // return a 401 unauthorised error... - } else { - JsonWebToken.verify(jwt, getKey, {}, function (err, decoded) { - let decodedJWT = decoded; - // microservices auth is successful.. - }); - } - } - } catch (err) { - next(err); - } - // highlight-end -}); -``` - -- Notice that we add the `sessionRequired: false` option when calling `getSession`. This is because in case the input tokens are from another microservice, then instead of throwing an unauthorised error, the `getSession` function will return `undefined`. It's important to note that if the session does exist, but the access token has expired, the `getSession` function will throw a try refresh token error, sending a 401 to the frontend. This will trigger a session refresh flow as expected. -- If the `getSession` function returns `undefined`, it means that the session is not from the frontend and we can attempt a microservice auth verification using the JWT verification method shown previously in this page. -- If that fails too, we can send back a `401` response. - - - - -```python -from supertokens_python.recipe.session.asyncio import get_session -from django.http import HttpRequest -from typing import Union - -async def like_comment(request: HttpRequest): - session = await get_session(request, session_required=False) - user_id = "" - if session is not None: - user_id = session.get_user_id() - else: - jwt: Union[str, None] = request.headers.get("Authorization") # type: ignore - if jwt is None: - # return a 401 unauthorised error... - pass - else: - jwt = jwt.split("Bearer ")[1] - # JWT verification (see previous step) - pass - - print(user_id) # TODO -``` - -- Notice that we add the `sessionRequired: false` option when calling `getSession`. This is because in case the input tokens are from another microservice, then instead of throwing an unauthorised error, the `getSession` function will return `None`. It's important to note that if the session does exist, but the access token has expired, the `getSession` function will throw a try refresh token error, sending a 401 to the frontend. This will trigger a session refresh flow as expected. -- If the `getSession` function returns `None`, it means that the session is not from the frontend and we can attempt a microservice auth verification using the JWT verification method shown previously in this page. -- If that fails too, we can send back a `401` response. - - - - -```go -import ( - "fmt" - "net/http" - "strings" - - "github.com/supertokens/supertokens-golang/recipe/session" - "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" - "github.com/supertokens/supertokens-golang/supertokens" -) - -func likeCommentAPI(w http.ResponseWriter, r *http.Request) { - sessionRequired := false - sessionContainer, err := session.GetSession(r, w, &sessmodels.VerifySessionOptions{ - SessionRequired: &sessionRequired, - }) - - if err != nil { - err = supertokens.ErrorHandler(err, r, w) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - return - } - - if sessionContainer != nil { - userID := sessionContainer.GetUserID() - - // TODO: API logic... - fmt.Println(userID) - } else { - // Check for JWT in the Authorization header - authHeader := r.Header.Get("Authorization") - if authHeader == "" { - // Return 401 Unauthorized error - w.WriteHeader(http.StatusUnauthorized) - return - } - - // Split the Authorization header to get the JWT - parts := strings.Split(authHeader, " ") - if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { - // Return 401 Unauthorized error - w.WriteHeader(http.StatusUnauthorized) - return - } - - jwt := parts[1] - - fmt.Println(jwt) - - // verify JWT based on code snippet here: https://gist.github.com/rishabhpoddar/8c26ed237add1a5b86481e72032abf8d - } -} -``` - -- Notice that we add the `sessionRequired: false` option when calling `getSession`. This is because in case the input tokens are from another microservice, then instead of throwing an unauthorised error, the `getSession` function will return `nil`. It's important to note that if the session does exist, but the access token has expired, the `getSession` function will return an error, and the `supertokens.ErrorHandler` will send a 401 to the frontend. This will trigger a session refresh flow as expected. -- If the `getSession` function returns `nil`, it means that the session is not from the frontend and we can attempt a microservice auth verification using the JWT verification method shown previously in this page. -- If that fails too, we can send back a `401` response. - - - - - \ No newline at end of file diff --git a/v2/microservice_auth/legacy/implementation-guide.mdx b/v2/microservice_auth/legacy/implementation-guide.mdx new file mode 100644 index 000000000..a6f98d49a --- /dev/null +++ b/v2/microservice_auth/legacy/implementation-guide.mdx @@ -0,0 +1,659 @@ +--- +id: implementation-guide +title: Implementation Guide +hide_title: true +--- + +import WithWithoutSDK from "/src/components/tabs/WithWithoutSDK"; +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; +import TabItem from '@theme/TabItem'; +import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs" +import AppInfoForm from "/src/components/appInfoForm" + +# Legacy Flow + + +This custom flow makes use of the **SuperTokens Core** without adhering to the **OAuth2** standard. +It involves creating private access tokens and passing them to the microservices. + +Microservice auth flow diagram + +The authentication sequene will work in the following way: + +1. Microservice `M1` requests a JWT (JSON Web Token) from the **SuperTokens Core** +2. Microservice `M1` sends the JWT to `M2` +3. Microservice `M2` will verifies the JWT and completes the action if the JWT is valid + + +## Creating a JWT + +### When to create a JWT? +The first step is to create a JWT from the microservice that will be sending the request (let's call this microservice `M1`). This JWT will be verified by other microservices when `M1` sends them a request. Since this JWT remains static per microservice, the best time to create this is on process starts - i.e. when `M1` starts. + +### What to add in the JWT? +The JWT can contain any information you like, but at a minimum, it needs to contain information proving that it is a microservice that is allowed to query other microservices in your infrastructure. This is needed since you may issue a JWT to an end user as well, and they should not be able to query any microservice directly. + +We can add the following claim in the JWT to "mark" the JWT as one that is meant for microservice auth only: +```json +{..., "source": "microservice", ...} +``` + +In the receiving microservice (`M2`), we can then [verify the JWT](./jwt-verification/index) and check that this claim is present before serving the request. + + +### Code for creating a JWT + + + + +First, we need to initialise the `JWT` recipe in the `supertokens.init` function call: + + + + +```tsx +import supertokens from "supertokens-node" +import jwt from "supertokens-node/recipe/jwt" + +supertokens.init({ + appInfo: { + apiDomain: "...", + appName: "...", + websiteDomain: "..." + }, + supertokens: { + connectionURI: "...", // location of the core + apiKey: "..." // provide the core's API key if configured + }, + recipeList: [ + // highlight-next-line + jwt.init() + ] +}) +``` + + + + +```go +import ( + "github.com/supertokens/supertokens-golang/recipe/jwt" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func main() { + supertokens.Init(supertokens.TypeInput{ + AppInfo: supertokens.AppInfo{ + AppName: "...", + WebsiteDomain: "...", + APIDomain: "...", + }, + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "...", // location of the core + APIKey: "...", // provide the core's API key if configured + }, + RecipeList: []supertokens.Recipe{ + // highlight-next-line + jwt.Init(nil), + }, + }) +} +``` + + + + +```python +from supertokens_python import init, InputAppInfo, SupertokensConfig +from supertokens_python.recipe import jwt + +init( + app_info=InputAppInfo( + app_name="...", + api_domain="...", + website_domain="...", + ), + supertokens_config=SupertokensConfig( + connection_uri="...", # location of the core + api_key="..." # provide the core's API key if configured + ), + framework='django', + recipe_list=[ + # highlight-next-line + jwt.init(), + ], +) +``` + + + + +:::important +- The value of `apiDomain` should be the domain part of the JWKS URL that will be used to verify the JWT (from `M2`). This should ideally be the domain of the microservice that has all the other SuperTokens' recipes initialised in them. +- If this microservice does not initialise any other recipe, the values of `appName` and `websiteDomain` don't matter. +::: + +After this, you can use the JWT recipe to create your own JWT whenever required: + + + + +```tsx +import jwt from "supertokens-node/recipe/jwt" + +async function createJWT(payload: any) { + let jwtResponse = await jwt.createJWT({ + ...payload, + source: "microservice" + }); + if (jwtResponse.status === "OK") { + // Send JWT as Authorization header to M2 + return jwtResponse.jwt; + } + throw new Error("Unable to create JWT. Should never come here.") +} +``` + + + + +```go +import ( + "fmt" + + "github.com/supertokens/supertokens-golang/recipe/jwt" +) + +func main() { + jwtResponse, err := jwt.CreateJWT(map[string]interface{}{ + "source": "microservice", + // ...additional payload + }, nil, nil) + if err != nil { + // handle error + } + jwtString := jwtResponse.OK.Jwt + fmt.Println(jwtString) + // Send JWT as Authorization header to M2 +} +``` + + + + + + + +```python +from supertokens_python.recipe.jwt import asyncio +from supertokens_python.recipe.jwt.interfaces import CreateJwtOkResult + +async def create_jwt(): + jwtResponse = await asyncio.create_jwt({ + "source": "microservice", + # ... extra payload + }) + + if isinstance(jwtResponse, CreateJwtOkResult): + _ = jwtResponse.jwt + # Send JWT as Authorization header to M2 + else: + raise Exception("Unable to create JWT. Should never come here.") +``` + + + + +```python +from supertokens_python.recipe.jwt.syncio import create_jwt +from supertokens_python.recipe.jwt.interfaces import CreateJwtOkResult + +jwtResponse = create_jwt({ + "source": "microservice", + # ... extra payload +}) + +if isinstance(jwtResponse, CreateJwtOkResult): + jwtStr = jwtResponse.jwt + # Send JWT as Authorization header to M2 +else: + raise Exception("Unable to create JWT. Should never come here.") +``` + + + + + + + +:::note +By default, the lifetime of the JWT will be a 100 years. You can pass a second argument to the `createJWT` function indicating a custom lifetime (in seconds) for the JWT. +::: + + +:::note +By default, the JWT is signed by a static key, not subject to the key rotation normally applied to access tokens. You can pass `false` as the third argument to the `createJWT` function to use use the dynamic keys. +::: + + + + +You can send a `HTTP` request to the core as follows: + +```bash +curl --location --request POST '${connectionURI}/recipe/jwt' \ +--header 'rid: jwt' \ +--header 'api-key: ${APIKey}' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "payload": { + "source": "microservice", + ... + }, + "useStaticSigningKey": true, + "algorithm": "RS256", + "jwksDomain": "${apiDomain}", + "validity": ${validityInSeconds} +}' +``` +- The value of `${connectionURI}` is the core's location +- `${APIKey}` is the API key to query the core. This is only needed if an API key is configured on the core. +- The value of `${apiDomain}` is the domain on which the JWKs URLs will be served from +- `${validityInSeconds}` is the lifetime of the JWT + +An example response is as follows: +```json +{ + "status": "OK", + "jwt": "eyJraWQiOiI0YTE...rCFPcIRgzu_bChIIpFdA" +} + +``` + + + + +### What to do with the JWT? +Once the JWT is created, you can store it in a (globally accessible) variable and access it when you want to talk to a microservice. You can add the JWT as an `Authorization: Bearer` token like so: + +```bash +curl --location --request POST 'https://microservice_location/path' \ +--header 'Authorization: Bearer eyJraWQiOiI0YTE...rCFPcIRgzu_bChIIpFdA' \ +--header 'Content-Type: application/json; charset=utf-8' \ +--data-raw '{ + "request": "payload" +}' +``` + +## Verify JWTs + + + +When a target microservice receives a JWT, it must first verify it before proceeding to serve the request. There are two steps here: +- A standard verification of the JWT +- Checking the JWT claim to make sure that another microservice has queried it. + +### Standard verification of a JWT + +#### Method 1) Using JWKS endpoint + + + + +##### a) Get JWKS endpoint + +The JWKS endpoint is `{apiDomain}/{apiBasePath}/jwt/jwks.json`. Here the `apiDomain` and `apiBasePath` are values pointing to the server in which you have initalised SuperTokens using our backend SDK. + +##### b) Verify the JWT + +Some libraries let you provide a JWKS endpoint to verify a JWT. For example for NodeJS you can use `jsonwebtoken` and `jwks-rsa` together to achieve this. + +```ts +import JsonWebToken, { JwtHeader, SigningKeyCallback } from 'jsonwebtoken'; +import jwksClient from 'jwks-rsa'; + +var client = jwksClient({ + jwksUri: '^{form_apiDomain}^{form_apiBasePath}/jwt/jwks.json' +}); + +function getKey(header: JwtHeader, callback: SigningKeyCallback) { + client.getSigningKey(header.kid, function (err, key) { + var signingKey = key!.getPublicKey(); + callback(err, signingKey); + }); +} + +let jwt = "..."; +JsonWebToken.verify(jwt, getKey, {}, function (err, decoded) { + let decodedJWT = decoded; + // Use JWT +}); +``` + + + + +Refer to this [Github gist](https://gist.github.com/rishabhpoddar/ea31502923ec9a53136371f2b6317ffa) for a code reference of how use `PyJWK` to do JWT verification. The gist contains two files: +- `jwt_verification.py` (which you can just copy / paste into your application). You will have to modify the `JWKS_URI` in this file to point to your supertokens core instance (replacing the `try.supertokens.com` part of the URL). This file is written for `sync` python apps, and can be modified to work with `async` apps as well. + - This file essentially exposes a function called `verify_jwt` which takes an input JWT string. + - This function takes care of caching public keys in memory + auto refetching if the public keys have changed (which happens automatically every 24 hours with supertokens). This will not cause any user logouts, and is just a security feature. +- `views.py`: This is an example `GET` API which extracts the JWT token from the authorization header in the request and calls the `verify_jwt` function from the other file. + + + + +Refer to this [Github gist](https://gist.github.com/rishabhpoddar/8c26ed237add1a5b86481e72032abf8d) for a code reference of how use the Golang `jwt` lib to do session verification. The gist contains two files: +- `verifyToken.go` (which you can just copy / paste into your application). You will have to modify the `coreUrl` in this file to point to your supertokens core instance (replacing the `try.supertokens.com` part of the URL). + - This file essentially exposes a function called `GetJWKS` which returns a reference to the JWKS public keys that can be used for JWT verification. + - This function takes care of caching public keys in memory + auto refetching if the public keys have changed (which happens automatically every 24 hours with supertokens). This will not cause any user logouts, and is just a security feature. +- `main.go`: This is an example of how to verify a JWT using the golang JWT verification lib along with our helper function to get the JWKs keys. + + + + +#### Method 2) Using public key string + + + + +Some JWT verification libraries require you to provide the JWT secret / public key for verification. You can obtain the JWT secret from SuperTokens in the following way: + +- First, we query the `JWKS.json` endpoint: + ```bash + curl --location --request GET '^{form_apiDomain}^{form_apiBasePath}/jwt/jwks.json' + + { + "keys": [ + { + "kty": "RSA", + "kid": "s-2de612a5-a5ba-413e-9216-4c43e2e78c86", + "n": "AMZruthvYz7Ft-Dp0BC_SEEJaWK91s_YA-RR81iLJ6BTT6gJp0CcV4DfBynFU_59dRGOZyVQpAW6Drnc_6LyZpVWHROzqt-Fjh8TAqodayhPJVuZt25eQiYrqcaK_dnuHrm8qwUq-hko6q1o1o9NIIZWNfUBEVWmNhyAJFk5bi3pLwtKPYrUQzVLcTdDUe4SIltvvfpYHbVFnYtxkBVmqO68j7sI8ktmTXM_heals-W6WmozabDkC9_ITCeRat2f7A2l0t4QzO0ZCzZcJfhusF4X1niKgY6yYXpbX6is4HCfhYfdabcE52xYMNl-gw9XDjsIxfBMUDvOFRHWlx0rU8c=", + "e": "AQAB", + "alg": "RS256", + "use": "sig" + }, + { + "kty": "RSA", + "kid": "d-230...802340", + "n": "AMZruthvYz7...lx0rU8c=", + "e": "...", + "alg": "RS256", + "use": "sig" + } + ] + } + ``` + + :::important + The above shows an example output which returns two keys. There could be more keys returned based on the configured key rotation setting in the core. If you notice, each key's `kid` starts with a `s-..` or a `d-..`. The `s-..` key is a static key that will never change, whereas `d-...` keys are dynamic keys that keep changing. So if you are hardcoding public keys somewhere, you always want to pick the `s-..` key. + ::: + +- Next, we run the NodeJS script below to convert the above output to a `PEM` file format. + ```tsx + import jwkToPem from 'jwk-to-pem'; + + // This JWK is copied from the result of the above SuperTokens core request + let jwk = { + "kty": "RSA", + "kid": "s-2de612a5-a5ba-413e-9216-4c43e2e78c86", + "n": "AMZruthvYz7Ft-Dp0BC_SEEJaWK91s_YA-RR81iLJ6BTT6gJp0CcV4DfBynFU_59dRGOZyVQpAW6Drnc_6LyZpVWHROzqt-Fjh8TAqodayhPJVuZt25eQiYrqcaK_dnuHrm8qwUq-hko6q1o1o9NIIZWNfUBEVWmNhyAJFk5bi3pLwtKPYrUQzVLcTdDUe4SIltvvfpYHbVFnYtxkBVmqO68j7sI8ktmTXM_heals-W6WmozabDkC9_ITCeRat2f7A2l0t4QzO0ZCzZcJfhusF4X1niKgY6yYXpbX6is4HCfhYfdabcE52xYMNl-gw9XDjsIxfBMUDvOFRHWlx0rU8c=", + "e": "AQAB", + "alg": "RS256", + "use": "sig" + }; + + // @ts-ignore + let certString = jwkToPem(jwk); + ``` + + The above snippet would generate the following certificate string: + + ```text + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxmu62G9jPsW34OnQEL9I + QQlpYr3Wz9gD5FHzWIsnoFNPqAmnQJxXgN8HKcVT/n11EY5nJVCkBboOudz/ovJm + ... (truncated for display) + XhfWeIqBjrJheltfqKzgcJ+Fh91ptwTnbFgw2X6DD1cOOwjF8ExQO84VEdaXHStT + xwIDAQAB + -----END PUBLIC KEY----- + ``` + +- Now you can use the generated PEM string in your code like shown below: + ```ts + import JsonWebToken from 'jsonwebtoken'; + + // Truncated for display + let certificate = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki...\n-----END PUBLIC KEY-----"; + let jwt = "..."; // fetch the JWT from sAccessToken cookie or Authorization Bearer header + JsonWebToken.verify(jwt, certificate, function (err, decoded) { + let decodedJWT = decoded; + // Use JWT + }); + ``` + + + + +:::caution +Not applicable. Please use method 1 instead. +::: + + + + +:::caution +Not applicable. Please use method 1 instead. +::: + + + + +### Claim verification +The second step is to get the JWT payload and check that it has the `"source": "microservice"` claim: + + + + +```tsx +import JsonWebToken, { JwtHeader, SigningKeyCallback } from 'jsonwebtoken'; +import jwksClient from 'jwks-rsa'; + +var client = jwksClient({ + jwksUri: '^{form_apiDomain}^{form_apiBasePath}/jwt/jwks.json' +}); + +function getKey(header: JwtHeader, callback: SigningKeyCallback) { + client.getSigningKey(header.kid, function (err, key) { + var signingKey = key!.getPublicKey(); + callback(err, signingKey); + }); +} + +let jwt = "..."; +JsonWebToken.verify(jwt, getKey, {}, function (err, decoded) { + // highlight-start + let decodedJWT = decoded; + if (decodedJWT === undefined || typeof decodedJWT === "string" || decodedJWT.source === undefined || decodedJWT.source !== "microservice") { + // return a 401 unauthorised error + } else { + // handle API request... + } + // highlight-end +}); +``` + + + + +Referring once again to this [Github gist](https://gist.github.com/rishabhpoddar/ea31502923ec9a53136371f2b6317ffa), we can see in `views.py`, between lines 20 and 28, that we are checking for the value of a certain claim in the decoded JWT payload. You can do something similar and check for the `source` claim whose value must be `microservice`. If it is, then all's good, else you can return a 401. + + + + +Referring once again to this [Github gist](https://gist.github.com/rishabhpoddar/8c26ed237add1a5b86481e72032abf8d), we can see in `main.go`, between lines 32 and 44, that we are checking for the value of a certain claim in the decoded JWT payload. You can do something similar and check for the `source` claim whose value must be `microservice`. If it is, then all's good, else you can return a 401. + + + + +### M2M and frontend session verification for the same API +You may have a setup wherein the same API is called from the frontend as well as from other microservices. The frontend session works differently than m2m sessions, so we have to account for both forms of token inputs. + +Our approach here would be to first attempt frontend session verification, and if that fails, then attempt m2m jwt verification (using the above method). If both fails, then we send back a `401` response. + +We will use the [`getSession` function](https://supertokens.com/docs/session/common-customizations/sessions/session-verification-in-api/get-session) for frontend session verification. + + + + +```tsx +import express from "express"; +import Session from "supertokens-node/recipe/session"; +import JsonWebToken, { JwtHeader, SigningKeyCallback } from 'jsonwebtoken'; +import jwksClient from 'jwks-rsa'; + +let app = express(); + + +var client = jwksClient({ + jwksUri: '^{form_apiDomain}^{form_apiBasePath}/jwt/jwks.json' +}); + +function getKey(header: JwtHeader, callback: SigningKeyCallback) { + client.getSigningKey(header.kid, function (err, key) { + var signingKey = key!.getPublicKey(); + callback(err, signingKey); + }); +} + + +app.post("/like-comment", async (req, res, next) => { + // highlight-start + try { + let session = await Session.getSession(req, res, { sessionRequired: false }) + + if (session !== undefined) { + // API call from the frontend and session verification is successful.. + let userId = session.getUserId(); + } else { + // maybe this API is called from a microservice, so we attempt JWT verification as + // shown above. + let jwt = req.headers["authorization"]; + jwt = jwt === undefined ? undefined : jwt.split('Bearer ')[1]; + if (jwt === undefined) { + // return a 401 unauthorised error... + } else { + JsonWebToken.verify(jwt, getKey, {}, function (err, decoded) { + let decodedJWT = decoded; + // microservices auth is successful.. + }); + } + } + } catch (err) { + next(err); + } + // highlight-end +}); +``` + +- Notice that we add the `sessionRequired: false` option when calling `getSession`. This is because in case the input tokens are from another microservice, then instead of throwing an unauthorised error, the `getSession` function will return `undefined`. It's important to note that if the session does exist, but the access token has expired, the `getSession` function will throw a try refresh token error, sending a 401 to the frontend. This will trigger a session refresh flow as expected. +- If the `getSession` function returns `undefined`, it means that the session is not from the frontend and we can attempt a microservice auth verification using the JWT verification method shown previously in this page. +- If that fails too, we can send back a `401` response. + + + + +```python +from supertokens_python.recipe.session.asyncio import get_session +from django.http import HttpRequest +from typing import Union + +async def like_comment(request: HttpRequest): + session = await get_session(request, session_required=False) + user_id = "" + if session is not None: + user_id = session.get_user_id() + else: + jwt: Union[str, None] = request.headers.get("Authorization") # type: ignore + if jwt is None: + # return a 401 unauthorised error... + pass + else: + jwt = jwt.split("Bearer ")[1] + # JWT verification (see previous step) + pass + + print(user_id) # TODO +``` + +- Notice that we add the `sessionRequired: false` option when calling `getSession`. This is because in case the input tokens are from another microservice, then instead of throwing an unauthorised error, the `getSession` function will return `None`. It's important to note that if the session does exist, but the access token has expired, the `getSession` function will throw a try refresh token error, sending a 401 to the frontend. This will trigger a session refresh flow as expected. +- If the `getSession` function returns `None`, it means that the session is not from the frontend and we can attempt a microservice auth verification using the JWT verification method shown previously in this page. +- If that fails too, we can send back a `401` response. + + + + +```go +import ( + "fmt" + "net/http" + "strings" + + "github.com/supertokens/supertokens-golang/recipe/session" + "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/supertokens" +) + +func likeCommentAPI(w http.ResponseWriter, r *http.Request) { + sessionRequired := false + sessionContainer, err := session.GetSession(r, w, &sessmodels.VerifySessionOptions{ + SessionRequired: &sessionRequired, + }) + + if err != nil { + err = supertokens.ErrorHandler(err, r, w) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + return + } + + if sessionContainer != nil { + userID := sessionContainer.GetUserID() + + // TODO: API logic... + fmt.Println(userID) + } else { + // Check for JWT in the Authorization header + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + // Return 401 Unauthorized error + w.WriteHeader(http.StatusUnauthorized) + return + } + + // Split the Authorization header to get the JWT + parts := strings.Split(authHeader, " ") + if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { + // Return 401 Unauthorized error + w.WriteHeader(http.StatusUnauthorized) + return + } + + jwt := parts[1] + + fmt.Println(jwt) + + // verify JWT based on code snippet here: https://gist.github.com/rishabhpoddar/8c26ed237add1a5b86481e72032abf8d + } +} +``` + +- Notice that we add the `sessionRequired: false` option when calling `getSession`. This is because in case the input tokens are from another microservice, then instead of throwing an unauthorised error, the `getSession` function will return `nil`. It's important to note that if the session does exist, but the access token has expired, the `getSession` function will return an error, and the `supertokens.ErrorHandler` will send a 401 to the frontend. This will trigger a session refresh flow as expected. +- If the `getSession` function returns `nil`, it means that the session is not from the frontend and we can attempt a microservice auth verification using the JWT verification method shown previously in this page. +- If that fails too, we can send back a `401` response. + + + + + diff --git a/v2/microservice_auth/legacy/security-analysis.mdx b/v2/microservice_auth/legacy/security-analysis.mdx new file mode 100644 index 000000000..e20b9676a --- /dev/null +++ b/v2/microservice_auth/legacy/security-analysis.mdx @@ -0,0 +1,28 @@ +--- +id: security-analysis +title: Security Analysis +hide_title: true +--- + +# Security Analysis of microservice auth + +## Who can query the microservices? +Anyone or any service that has direct access to the SuperTokens core can produce a valid JWT and query your microservices. If the core is being exposed to the internet, you *must* add an API key to protect it. + +Even though end users may be issued a JWT for their session (that is signed by the core), they cannot query a microservice directly since their JWT *should* not have the `source: "microservice"` claim in it. + +## What happens if the core's API key is compromised? +Then the attacker can issue their own JWTs to be able to query your microservices. To limit this protection, you may want to add firewall rules to allow access to the core only from services on your backend. + +You can also provide multiple API keys to the core and give a unique key to each microservice in your infrastructure. This way, it would be easier to track where a leak came from. + +## What happens if the JWT signing key is compromised? +In this case, the attacker could fabricate their own JWT to be able to query your microservices. To limit this risk, we plan on implementing JWT signing key rotation methodology, but until then, you can limit the reachability of your microservices based on the request's IP address. + +## How to limit which microservice can query another one? +If an organisation has several teams and several microservices, it is common to limit which other services a given microservice can query. For example, if there exists `M1`, `M2` and `M3` microservices, we may have a situation in which we want `M1` to only be able to query `M2` and not `M3`. + +With just one SuperTokens core deployment, it is not possible to have this type of restriction, since all the microservices create and verify their JWTs using that same public / private keys. So if `M3` recieves a request, it has no way of reliably knowing if the request is from `M1` or `M2` (assuming that IP based access control is not implemented). + +We can achieve this type of restriction by deploying multiple cores connected to their own databases. In our example, we can deploy a dedicated SuperTokens core for `M3`'s auth, such that only `M3` uses that to verify the incoming JWTs. Then, only other services that have access to that core can create JWTs that `M3` will accept. So if `M1` doesn't have access to `M3`'s core's API key, then we can be assured that successful requests to `M3` are not from `M1`. + diff --git a/v2/microservice_auth/security-analysis.mdx b/v2/microservice_auth/security-analysis.mdx index 94f616335..8313f7491 100644 --- a/v2/microservice_auth/security-analysis.mdx +++ b/v2/microservice_auth/security-analysis.mdx @@ -4,27 +4,6 @@ title: Security Analysis hide_title: true --- -# Security Analysis of microservice auth +import Redirector from '/src/components/Redirector'; -## Who can query the microservices? -Anyone or any service that has direct access to the SuperTokens core can produce a valid JWT and query your microservices. If the core is being exposed to the internet, you *must* add an API key to protect it. - -Even though end users may be issued a JWT for their session (that is signed by the core), they cannot query a microservice directly since their JWT *should* not have the `source: "microservice"` claim in it. - -## What happens if the core's API key is compromised? -Then the attacker can issue their own JWTs to be able to query your microservices. To limit this protection, you may want to add firewall rules to allow access to the core only from services on your backend. - -You can also provide multiple API keys to the core and give a unique key to each microservice in your infrastructure. This way, it would be easier to track where a leak came from. - -## What happens if the JWT signing key is compromised? -In this case, the attacker could fabricate their own JWT to be able to query your microservices. To limit this risk, we plan on implementing JWT signing key rotation methodology, but until then, you can limit the reachability of your microservices based on the request's IP address. - -## How to limit which microservice can query another one? -If an organisation has several teams and several microservices, it is common to limit which other services a given microservice can query. For example, if there exists `M1`, `M2` and `M3` microservices, we may have a situation in which we want `M1` to only be able to query `M2` and not `M3`. - -With just one SuperTokens core deployment, it is not possible to have this type of restriction, since all the microservices create and verify their JWTs using that same public / private keys. So if `M3` recieves a request, it has no way of reliably knowing if the request is from `M1` or `M2` (assuming that IP based access control is not implemented). - -We can achieve this type of restriction by deploying multiple cores connected to their own databases. In our example, we can deploy a dedicated SuperTokens core for `M3`'s auth, such that only `M3` uses that to verify the incoming JWTs. Then, only other services that have access to that core can create JWTs that `M3` will accept. So if `M1` doesn't have access to `M3`'s core's API key, then we can be assured that successful requests to `M3` are not from `M1`. - -## What about OAuth 2.0 client credential flow? -The common way to achieve microservice auth is via [OAuth's client credential flow](https://oauth.net/2/grant-types/client-credentials/). We are in the process of becoming an OAuth 2.0 provider and would be implmenting this flow. \ No newline at end of file + diff --git a/v2/microservice_auth/sidebars.js b/v2/microservice_auth/sidebars.js index eae525a94..e78498df0 100644 --- a/v2/microservice_auth/sidebars.js +++ b/v2/microservice_auth/sidebars.js @@ -1,21 +1,12 @@ module.exports = { sidebar: [ "introduction", + "client-credentials", { type: "category", - label: "Implementation", + label: "Legacy Flow", collapsed: false, - items: [ - "jwt-creation", - { - type: "category", - label: "JWT Verification", - items: [ - "jwt-verification/index" - ] - }, - ] + items: ["legacy/implementation-guide", "legacy/security-analysis"], }, - "security-analysis" - ] + ], }; diff --git a/v2/passwordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/passwordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx index fd453819d..d154a65b7 100644 --- a/v2/passwordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/passwordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -347,7 +347,7 @@ import { backendConfig } from "@/app/config/backend"; SuperTokens.init(backendConfig()); // in the app/api/auth/[...path]/route.ts file -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); const withCustomErrorHandling = async (request: NextRequest) => { try { diff --git a/v2/passwordless/common-customizations/email-verification/about.mdx b/v2/passwordless/common-customizations/email-verification/about.mdx index 63b5e5ff1..ceb8fd555 100644 --- a/v2/passwordless/common-customizations/email-verification/about.mdx +++ b/v2/passwordless/common-customizations/email-verification/about.mdx @@ -17,6 +17,7 @@ import TabItem from "@theme/TabItem"; import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import AngularUIImplementation from "/src/components/reusableSnippets/angularUIImplementation" import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Enable email verification @@ -25,6 +26,8 @@ import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplement Email verification is turned off by default. It is strongly encouraged to enable it to ensure the authenticity of your users. ::: + + diff --git a/v2/passwordless/common-customizations/email-verification/protecting-routes.mdx b/v2/passwordless/common-customizations/email-verification/protecting-routes.mdx index b8759e741..6e13b1a87 100644 --- a/v2/passwordless/common-customizations/email-verification/protecting-routes.mdx +++ b/v2/passwordless/common-customizations/email-verification/protecting-routes.mdx @@ -24,11 +24,14 @@ import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import AngularUIImplementation from "/src/components/reusableSnippets/angularUIImplementation" import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" import {Answer} from "/src/components/question" +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting backend APIs and website routes ## Protecting backend API routes + + ### Add email verification checks to all API routes If you want to protect all your backend API routes with email verification checks, set the `mode` to `REQUIRED` in the `EmailVerification` config. Routes protected with the `verifySession` middleware will now additionally check for email verification status. diff --git a/v2/passwordless/common-customizations/sessions/claims/access-token-payload.mdx b/v2/passwordless/common-customizations/sessions/claims/access-token-payload.mdx index 3563fef3a..f0290e6cf 100644 --- a/v2/passwordless/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/passwordless/common-customizations/sessions/claims/access-token-payload.mdx @@ -22,6 +22,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 1. Using the access token payload +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## Add custom claims to the access token payload :::important diff --git a/v2/passwordless/common-customizations/sessions/claims/claim-validators.mdx b/v2/passwordless/common-customizations/sessions/claims/claim-validators.mdx index fdc63bbb1..21f55d69d 100644 --- a/v2/passwordless/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/passwordless/common-customizations/sessions/claims/claim-validators.mdx @@ -23,6 +23,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 2. Using claim validators +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## What are session claims? SuperTokens session has a property called `accessTokenPayload`. This is a `JSON` object that's stored in a user's session which can be accessed on the frontend and backend. The key-values in this JSON payload are called claims. diff --git a/v2/passwordless/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/passwordless/common-customizations/sessions/protecting-frontend-routes.mdx index 232bba79f..9e5719247 100644 --- a/v2/passwordless/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/passwordless/common-customizations/sessions/protecting-frontend-routes.mdx @@ -14,9 +14,12 @@ import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthFrontendVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting frontend routes + + diff --git a/v2/passwordless/common-customizations/sessions/session-verification-in-api/get-session.mdx b/v2/passwordless/common-customizations/sessions/session-verification-in-api/get-session.mdx index 94a21fa6d..ce8decce4 100644 --- a/v2/passwordless/common-customizations/sessions/session-verification-in-api/get-session.mdx +++ b/v2/passwordless/common-customizations/sessions/session-verification-in-api/get-session.mdx @@ -13,10 +13,13 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session Verification using `getSession` + + If you want to use a non-middleware form of `verifySession`, you can use the `getSession` function. ## Using `getSession` diff --git a/v2/passwordless/common-customizations/sessions/session-verification-in-api/verify-session.mdx b/v2/passwordless/common-customizations/sessions/session-verification-in-api/verify-session.mdx index f176eab66..e7a1df653 100644 --- a/v2/passwordless/common-customizations/sessions/session-verification-in-api/verify-session.mdx +++ b/v2/passwordless/common-customizations/sessions/session-verification-in-api/verify-session.mdx @@ -10,10 +10,13 @@ import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" ## Verifying a session using the `verifySession` middleware + + For your APIs that require a user to be logged in, use the `verifySession` middleware: diff --git a/v2/passwordless/common-customizations/sessions/ssr.mdx b/v2/passwordless/common-customizations/sessions/ssr.mdx index 863e5cb5b..e89210f16 100644 --- a/v2/passwordless/common-customizations/sessions/ssr.mdx +++ b/v2/passwordless/common-customizations/sessions/ssr.mdx @@ -17,9 +17,12 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session verification during server side rendering + + :::important Getting access to the session during server side rendering is only possible using cookie-based sessions. This is the default setting, but you have to keep this in mind if you want to switch to header-based sessions. ::: diff --git a/v2/passwordless/common-customizations/sessions/with-jwt/jwt-verification.mdx b/v2/passwordless/common-customizations/sessions/with-jwt/jwt-verification.mdx index d1d014c9c..c39494207 100644 --- a/v2/passwordless/common-customizations/sessions/with-jwt/jwt-verification.mdx +++ b/v2/passwordless/common-customizations/sessions/with-jwt/jwt-verification.mdx @@ -15,14 +15,18 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import AppInfoForm from "/src/components/appInfoForm"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Manually verify the JWT + + There are three steps in doing session verification using JWTs: - Verify the JWT signature and expiry using a JWT verification library - Check for custom claim values for authorization. - Preventing CSRF attacks in case you are using cookies to store the JWT. + ## Verifying a JWT using a jwt verification library diff --git a/v2/passwordless/common-customizations/sessions/with-jwt/read-jwt.mdx b/v2/passwordless/common-customizations/sessions/with-jwt/read-jwt.mdx index 6406c10f2..22a344607 100644 --- a/v2/passwordless/common-customizations/sessions/with-jwt/read-jwt.mdx +++ b/v2/passwordless/common-customizations/sessions/with-jwt/read-jwt.mdx @@ -20,9 +20,12 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Fetching the access token string + + ## On the backend diff --git a/v2/passwordless/custom-ui/enable-email-verification.mdx b/v2/passwordless/custom-ui/enable-email-verification.mdx index 3a593c75c..7fca5151e 100644 --- a/v2/passwordless/custom-ui/enable-email-verification.mdx +++ b/v2/passwordless/custom-ui/enable-email-verification.mdx @@ -19,6 +19,7 @@ import {Answer} from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; import TabItem from "@theme/TabItem"; +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; @@ -28,6 +29,8 @@ There are two modes of email verification: - `REQUIRED`: Requires that the user's email is verified before they can access your application's frontend or backend routes (that are protected with a session). - `OPTIONAL`: Adds information about email verification into the session, but leaves it up to you to enforce it on the backend and frontend based on your business logic. + + ## Step 1: Backend setup diff --git a/v2/passwordless/custom-ui/handling-session-tokens.mdx b/v2/passwordless/custom-ui/handling-session-tokens.mdx index 5c8cb0294..bdfa1a21f 100644 --- a/v2/passwordless/custom-ui/handling-session-tokens.mdx +++ b/v2/passwordless/custom-ui/handling-session-tokens.mdx @@ -9,6 +9,7 @@ hide_title: true import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; @@ -23,6 +24,7 @@ There are two modes ways in which you can use sessions with SuperTokens: Our frontend SDK uses `httpOnly` cookie based session for websites by default as it secures against tokens theft via XSS attacks. For other platform like mobile apps, we use a bearer token in the Authorization header by default. This setting can be changed [as described in the token transfer section](../common-customizations/sessions/token-transfer-method) + ## If using our frontend SDK @@ -41,9 +43,9 @@ Our frontend SDK handles everything for you. You only need to make sure that you Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. -:::note By default, our web SDKs use cookies to provide credentials. -::: + + diff --git a/v2/passwordless/custom-ui/securing-routes.mdx b/v2/passwordless/custom-ui/securing-routes.mdx index 00f7e4f61..b33427557 100644 --- a/v2/passwordless/custom-ui/securing-routes.mdx +++ b/v2/passwordless/custom-ui/securing-routes.mdx @@ -9,6 +9,7 @@ hide_title: true import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import TabItem from '@theme/TabItem'; import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" @@ -25,6 +26,8 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + + ### Requiring an active session For your APIs that require a user to be logged in, use the `verifySession` middleware @@ -437,7 +440,7 @@ In case of successful session verification, you get access to a `session` object ### Microservice authentication -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/introduction). +For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/unified-login/machine-to-machine-authentication). diff --git a/v2/passwordless/graphql-integration/backend-setup.mdx b/v2/passwordless/graphql-integration/backend-setup.mdx index a521f7578..ec91bb6e9 100644 --- a/v2/passwordless/graphql-integration/backend-setup.mdx +++ b/v2/passwordless/graphql-integration/backend-setup.mdx @@ -9,11 +9,14 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Backend Setup ## Complete Quick Setup + + @@ -30,6 +33,7 @@ Follow the [frontend and backend custom UI setup guides](../custom-ui/init/front + diff --git a/v2/passwordless/hasura-integration/with-jwt.mdx b/v2/passwordless/hasura-integration/with-jwt.mdx index 31e7c262c..e7a2aeb57 100644 --- a/v2/passwordless/hasura-integration/with-jwt.mdx +++ b/v2/passwordless/hasura-integration/with-jwt.mdx @@ -23,6 +23,15 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" Using SuperTokens with Hasura requires you to host your own API layer that uses our Backend SDK. If you do not want to host your own server you can use a serverless environment (AWS Lambda for example) to achieve this. ::: +:::caution + +This guide will only work if you are using **SuperTokens Session Tokens**. + +If you are implementing an **OAuth2** based feature, like **Microservice Authentication** or **Unified Login**, the method of adding custom claims is different. +Please check our separate [page](/docs/unified-login/customizations/add-custom-claims-in-tokens) that shows you how to do this. + +::: + ## 1) Complete the setup guides diff --git a/v2/passwordless/nestjs/guide.mdx b/v2/passwordless/nestjs/guide.mdx index c17f44b65..128c75c4c 100644 --- a/v2/passwordless/nestjs/guide.mdx +++ b/v2/passwordless/nestjs/guide.mdx @@ -12,6 +12,7 @@ import BackendDeliveryMethod from "../../passwordless/reusableMD/backendDelivery import AppInfoForm from "/src/components/appInfoForm" import CoreInjector from "/src/components/coreInjector" import {Question, Answer} from "/src/components/question" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # NestJS Integration Guide @@ -357,6 +358,8 @@ bootstrap(); ## Add a session verification guard + + Now that the library is set up, you can add a guard to protect your API. You can scaffold this by running: `nest g guard auth`. In the newly created `auth.guard.ts` file, implement session verification: diff --git a/v2/passwordless/nextjs/app-directory/protecting-route.mdx b/v2/passwordless/nextjs/app-directory/protecting-route.mdx index e636f81f0..311b02a7b 100644 --- a/v2/passwordless/nextjs/app-directory/protecting-route.mdx +++ b/v2/passwordless/nextjs/app-directory/protecting-route.mdx @@ -9,16 +9,21 @@ hide_title: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import CoreInjector from "/src/components/coreInjector" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 4. Checking for sessions in frontend routes + + Protecting a website route means that it cannot be accessed unless a user is signed in. If a non signed in user tries to access it, they will be redirected to the login page. + + ## Sessions with Client Components Lets create a client component for the `/` route of our website. diff --git a/v2/passwordless/nextjs/app-directory/session-verification-middleware.mdx b/v2/passwordless/nextjs/app-directory/session-verification-middleware.mdx index a76ddce0d..4e4742164 100644 --- a/v2/passwordless/nextjs/app-directory/session-verification-middleware.mdx +++ b/v2/passwordless/nextjs/app-directory/session-verification-middleware.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # Using the Next.js middleware + + :::important This method is an alternative method for using sessions in an API. If you are already using [session guards](./session-verification-session-guard.mdx), you can skip this step. ::: diff --git a/v2/passwordless/nextjs/app-directory/session-verification-session-guard.mdx b/v2/passwordless/nextjs/app-directory/session-verification-session-guard.mdx index fb03ca4e0..663193a00 100644 --- a/v2/passwordless/nextjs/app-directory/session-verification-session-guard.mdx +++ b/v2/passwordless/nextjs/app-directory/session-verification-session-guard.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # Adding a session guard to each API route + + :::note This is applicable for when the frontend calls an API in the `/app/api` folder. ::: diff --git a/v2/passwordless/nextjs/app-directory/setting-up-backend.mdx b/v2/passwordless/nextjs/app-directory/setting-up-backend.mdx index b06c3ac05..c90c6f5e5 100644 --- a/v2/passwordless/nextjs/app-directory/setting-up-backend.mdx +++ b/v2/passwordless/nextjs/app-directory/setting-up-backend.mdx @@ -11,6 +11,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import AppInfoForm from "/src/components/appInfoForm" + # 3. Adding auth APIs We will add all the backend APIs for auth on `/api/auth`. This can be changed by setting the `apiBasePath` property in the `appInfo` object in the `appInfo.ts` file. For the rest of this page, we will assume you are using `/api/auth`. @@ -37,7 +38,7 @@ import { ensureSuperTokensInit } from '../../../config/backend'; ensureSuperTokensInit(); -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); export async function GET(request: NextRequest) { const res = await handleCall(request); diff --git a/v2/passwordless/nextjs/protecting-route.mdx b/v2/passwordless/nextjs/protecting-route.mdx index 21505368e..6de7c2164 100644 --- a/v2/passwordless/nextjs/protecting-route.mdx +++ b/v2/passwordless/nextjs/protecting-route.mdx @@ -9,9 +9,12 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import { OAuthFrontendVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # 4. Protecting a website route + + diff --git a/v2/passwordless/nextjs/session-verification/in-api.mdx b/v2/passwordless/nextjs/session-verification/in-api.mdx index e0d57c781..182b4d5d2 100644 --- a/v2/passwordless/nextjs/session-verification/in-api.mdx +++ b/v2/passwordless/nextjs/session-verification/in-api.mdx @@ -8,9 +8,13 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # 5a. Session verification in an API call + + :::note This is applicable for when the frontend calls an API in the `/pages/api` folder. ::: diff --git a/v2/passwordless/nextjs/session-verification/in-ssr.mdx b/v2/passwordless/nextjs/session-verification/in-ssr.mdx index fcdfb1c8d..4ed75df27 100644 --- a/v2/passwordless/nextjs/session-verification/in-ssr.mdx +++ b/v2/passwordless/nextjs/session-verification/in-ssr.mdx @@ -10,9 +10,12 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import CoreInjector from "/src/components/coreInjector" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 5b. Session verification in getServerSideProps + + :::note This is applicable for when verifying a session in `getServerSideProps` or `getInitialProps`. ::: diff --git a/v2/passwordless/pre-built-ui/enable-email-verification.mdx b/v2/passwordless/pre-built-ui/enable-email-verification.mdx index 8810f910f..59afbfc19 100644 --- a/v2/passwordless/pre-built-ui/enable-email-verification.mdx +++ b/v2/passwordless/pre-built-ui/enable-email-verification.mdx @@ -19,6 +19,7 @@ import {Answer} from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; import TabItem from "@theme/TabItem"; +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; @@ -28,6 +29,8 @@ There are two modes of email verification: - `REQUIRED`: Requires that the user's email is verified before they can access your application's frontend or backend routes (that are protected with a session). - `OPTIONAL`: Adds information about email verification into the session, but leaves it up to you to enforce it on the backend and frontend based on your business logic. + + ## Step 1: Backend setup diff --git a/v2/passwordless/pre-built-ui/further-reading/email-verification.mdx b/v2/passwordless/pre-built-ui/further-reading/email-verification.mdx index 6ceea4ed2..e05b3ee5a 100644 --- a/v2/passwordless/pre-built-ui/further-reading/email-verification.mdx +++ b/v2/passwordless/pre-built-ui/further-reading/email-verification.mdx @@ -7,6 +7,8 @@ hide_title: true +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" + # Email verification There are two modes of email verification: @@ -15,6 +17,8 @@ There are two modes of email verification: ## Information stored in session + + On sign in or sign up, SuperTokens adds information about the email verification status in the session's payload using the `EmailVerificationClaim`. It looks like this: ```json diff --git a/v2/passwordless/pre-built-ui/handling-session-tokens.mdx b/v2/passwordless/pre-built-ui/handling-session-tokens.mdx index 05314c1d7..69b80421f 100644 --- a/v2/passwordless/pre-built-ui/handling-session-tokens.mdx +++ b/v2/passwordless/pre-built-ui/handling-session-tokens.mdx @@ -10,6 +10,7 @@ hide_title: true import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; @@ -27,9 +28,9 @@ Our frontend SDK handles everything for you. You only need to make sure that you Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. -:::note By default, our web SDKs use cookies to provide credentials. -::: + + diff --git a/v2/passwordless/pre-built-ui/securing-routes.mdx b/v2/passwordless/pre-built-ui/securing-routes.mdx index 22d56941e..98c6911b0 100644 --- a/v2/passwordless/pre-built-ui/securing-routes.mdx +++ b/v2/passwordless/pre-built-ui/securing-routes.mdx @@ -14,6 +14,7 @@ import TabItem from '@theme/TabItem'; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import {Question, Answer}from "/src/components/question" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" # Securing your API and frontend routes @@ -24,6 +25,8 @@ import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" + + ### Requiring an active session For your APIs that require a user to be logged in, use the `verifySession` middleware @@ -436,7 +439,7 @@ In case of successful session verification, you get access to a `session` object ### Microservice authentication -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/introduction). +For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/unified-login/machine-to-machine-authentication). @@ -446,6 +449,14 @@ For authentication between microservices on your backend, checkout the [microser +:::caution + +These instructions only apply to scenarios in which you are using **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction) with **OAuth2 Access Tokens**, please check the [specific use case page](/docs/unified-login/introduction#when-to-use-unified-login) for relevant information. + +::: + diff --git a/v2/passwordless/serverless/with-aws-lambda/authorizer.mdx b/v2/passwordless/serverless/with-aws-lambda/authorizer.mdx index 02a71860c..b730b2d4f 100644 --- a/v2/passwordless/serverless/with-aws-lambda/authorizer.mdx +++ b/v2/passwordless/serverless/with-aws-lambda/authorizer.mdx @@ -10,9 +10,12 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Using Lambda Authorizers + + You can use a lambda as an Authorizer in API Gateways. This will enable you to use SuperTokens in a lambda to authorize requests to other integrations (e.g., AppSync). An Authorizer pointed to this lambda will add `context.authorizer.principalId` that you can map to a header. For example, you can map this to an "x-user-id" header which will be set to the id of the logged-in user. If there is no valid session for the request, this header won't exist. ## 1) Add configurations and dependencies diff --git a/v2/passwordless/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/passwordless/serverless/with-aws-lambda/jwt-authorizer.mdx index 86055a1d9..14f128558 100644 --- a/v2/passwordless/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/passwordless/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -16,7 +16,14 @@ import TabItem from '@theme/TabItem'; # Using JWT Authorizers :::caution + AWS supports JWT authorizers for HTTP APIs and not REST APIs on the API Gateway service. For REST APIs follow the [Lambda authorizer](./authorizer) guide + +This guide will work if you are using **SuperTokens Session Tokens**. + +If you implementing an **OAuth2** setup, through the [**Unified Login**](/docs/unified-login/introduction) or the [**Microservice Authentication**](/docs/microservice_auth/client-credentials) features, you will have to manually set the token audience property. +Please check the referenced pages for more information. + ::: ## 1) Add the `aud` claim in the JWT based on the authorizer configuration diff --git a/v2/passwordless/serverless/with-aws-lambda/session-verification.mdx b/v2/passwordless/serverless/with-aws-lambda/session-verification.mdx index 02857b5e7..ae4954e93 100644 --- a/v2/passwordless/serverless/with-aws-lambda/session-verification.mdx +++ b/v2/passwordless/serverless/with-aws-lambda/session-verification.mdx @@ -9,9 +9,12 @@ hide_title: true import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 3. Session verification / Building your APIs + + When building your own APIs, you may need to verify the session of the user before proceeding further. SuperTokens SDK exposes a `verifySession` function that can be utilized for this. In this guide, we will be creating a `/user` `GET` route that will return the current session information. ## 1) Add `/user` `GET` route in your API Gateway diff --git a/v2/passwordless/serverless/with-netlify/session-verification.mdx b/v2/passwordless/serverless/with-netlify/session-verification.mdx index 1fd4b1a1f..b28b7cd62 100644 --- a/v2/passwordless/serverless/with-netlify/session-verification.mdx +++ b/v2/passwordless/serverless/with-netlify/session-verification.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # 4. Session verification / Building your APIs + + For this guide, we will assume that we want an API `/.netlify/functions/user GET` which returns the current session information. ## 1) Create a new file `netlify/functions/user.js` diff --git a/v2/passwordless/user-roles/protecting-routes.mdx b/v2/passwordless/user-roles/protecting-routes.mdx index 9d497d1ca..a970a8ab3 100644 --- a/v2/passwordless/user-roles/protecting-routes.mdx +++ b/v2/passwordless/user-roles/protecting-routes.mdx @@ -19,9 +19,19 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting API and frontend routes +:::caution + +This guide applies to scenarios which involve **SuperTokens Session Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), that uses **OAuth2 Access Tokens**, please check our [separate page](/docs/unified-login/customizations/verify-tokens) that shows you how to validate them. +You will have to check for the `roles` claim in the token payload. + +::: + ## Protecting API routes In your API routes you: diff --git a/v2/session/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/session/advanced-customizations/apis-override/custom-response/throwing-error.mdx index fd453819d..d154a65b7 100644 --- a/v2/session/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/session/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -347,7 +347,7 @@ import { backendConfig } from "@/app/config/backend"; SuperTokens.init(backendConfig()); // in the app/api/auth/[...path]/route.ts file -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); const withCustomErrorHandling = async (request: NextRequest) => { try { diff --git a/v2/session/common-customizations/sessions/claims/access-token-payload.mdx b/v2/session/common-customizations/sessions/claims/access-token-payload.mdx index 081160e8e..f0290e6cf 100644 --- a/v2/session/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/session/common-customizations/sessions/claims/access-token-payload.mdx @@ -22,6 +22,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 1. Using the access token payload +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## Add custom claims to the access token payload :::important @@ -1259,4 +1267,4 @@ Future getJWT() async { - \ No newline at end of file + diff --git a/v2/session/common-customizations/sessions/claims/claim-validators.mdx b/v2/session/common-customizations/sessions/claims/claim-validators.mdx index fdc63bbb1..21f55d69d 100644 --- a/v2/session/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/session/common-customizations/sessions/claims/claim-validators.mdx @@ -23,6 +23,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 2. Using claim validators +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## What are session claims? SuperTokens session has a property called `accessTokenPayload`. This is a `JSON` object that's stored in a user's session which can be accessed on the frontend and backend. The key-values in this JSON payload are called claims. diff --git a/v2/session/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/session/common-customizations/sessions/protecting-frontend-routes.mdx index 232bba79f..9e5719247 100644 --- a/v2/session/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/session/common-customizations/sessions/protecting-frontend-routes.mdx @@ -14,9 +14,12 @@ import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthFrontendVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting frontend routes + + diff --git a/v2/session/common-customizations/sessions/session-verification-in-api/get-session.mdx b/v2/session/common-customizations/sessions/session-verification-in-api/get-session.mdx index fc2df03a1..ce8decce4 100644 --- a/v2/session/common-customizations/sessions/session-verification-in-api/get-session.mdx +++ b/v2/session/common-customizations/sessions/session-verification-in-api/get-session.mdx @@ -13,10 +13,13 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session Verification using `getSession` + + If you want to use a non-middleware form of `verifySession`, you can use the `getSession` function. ## Using `getSession` @@ -1460,4 +1463,4 @@ def verify_session( ``` - \ No newline at end of file + diff --git a/v2/session/common-customizations/sessions/session-verification-in-api/verify-session.mdx b/v2/session/common-customizations/sessions/session-verification-in-api/verify-session.mdx index a0b1b8488..e7a1df653 100644 --- a/v2/session/common-customizations/sessions/session-verification-in-api/verify-session.mdx +++ b/v2/session/common-customizations/sessions/session-verification-in-api/verify-session.mdx @@ -10,10 +10,13 @@ import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" ## Verifying a session using the `verifySession` middleware + + For your APIs that require a user to be logged in, use the `verifySession` middleware: @@ -1392,4 +1395,4 @@ async def like_comment(request: HttpRequest): :::tip feature You can also [build your own custom claim validators](../claims/claim-validators) based on your app's requirements. -::: \ No newline at end of file +::: diff --git a/v2/session/common-customizations/sessions/ssr.mdx b/v2/session/common-customizations/sessions/ssr.mdx index da3f38224..e89210f16 100644 --- a/v2/session/common-customizations/sessions/ssr.mdx +++ b/v2/session/common-customizations/sessions/ssr.mdx @@ -17,9 +17,12 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session verification during server side rendering + + :::important Getting access to the session during server side rendering is only possible using cookie-based sessions. This is the default setting, but you have to keep this in mind if you want to switch to header-based sessions. ::: @@ -184,4 +187,4 @@ If we redirect the user to the login page directly, then in the second case, the ## Can I use `verifySession` or `getSession` instead of manual JWT verification? Yes you can, but we do not recommend it because: - Often the webserver is on a different process than the API server. Using our backend SDK requires you to give it the credentials to the SuperTokens core, which you might not want to provide to the webserver. -- The `session` object resulting from `verifySession` and `getSession` makes it very easy to mutate the access token payload. These mutations are reflected on the frontend via network interceptors from our frontend SDK. But in SSR, the browser makes the call to your webserver direclty, so our frontend SDK interceptors don't run. This can cause inconsistency between the access token payload as seen on the frontend vs the backend. \ No newline at end of file +- The `session` object resulting from `verifySession` and `getSession` makes it very easy to mutate the access token payload. These mutations are reflected on the frontend via network interceptors from our frontend SDK. But in SSR, the browser makes the call to your webserver direclty, so our frontend SDK interceptors don't run. This can cause inconsistency between the access token payload as seen on the frontend vs the backend. diff --git a/v2/session/common-customizations/sessions/with-jwt/jwt-verification.mdx b/v2/session/common-customizations/sessions/with-jwt/jwt-verification.mdx index f0d888a8f..c39494207 100644 --- a/v2/session/common-customizations/sessions/with-jwt/jwt-verification.mdx +++ b/v2/session/common-customizations/sessions/with-jwt/jwt-verification.mdx @@ -15,14 +15,18 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import AppInfoForm from "/src/components/appInfoForm"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Manually verify the JWT + + There are three steps in doing session verification using JWTs: - Verify the JWT signature and expiry using a JWT verification library - Check for custom claim values for authorization. - Preventing CSRF attacks in case you are using cookies to store the JWT. + ## Verifying a JWT using a jwt verification library @@ -311,4 +315,4 @@ There are two methods for configuring [CSRF protection](../anti-csrf): ### Checking for anti-csrf when `VIA_TOKEN` is set When configured with `VIA_TOKEN`, an explicit `anti-csrf` token will be attached as a header to requests with `anti-csrf` as the key. To verify the `anti-csrf` token you will need to compare it the to value of the `antiCsrfToken` key from the payload of the decoded JWT. - \ No newline at end of file + diff --git a/v2/session/common-customizations/sessions/with-jwt/read-jwt.mdx b/v2/session/common-customizations/sessions/with-jwt/read-jwt.mdx index 34035905c..69aea6875 100644 --- a/v2/session/common-customizations/sessions/with-jwt/read-jwt.mdx +++ b/v2/session/common-customizations/sessions/with-jwt/read-jwt.mdx @@ -20,9 +20,12 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Fetching the access token string + + ## On the backend diff --git a/v2/session/graphql-integration/backend-setup.mdx b/v2/session/graphql-integration/backend-setup.mdx index 46c7c829c..e7db8d30b 100644 --- a/v2/session/graphql-integration/backend-setup.mdx +++ b/v2/session/graphql-integration/backend-setup.mdx @@ -7,6 +7,8 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # Backend Setup ## Complete Quick Setup @@ -15,6 +17,8 @@ Complete both steps from the quick setup guide for SuperTokens: - [Frontend Quick Setup](../quick-setup/frontend) - [Backend Quick Setup](../quick-setup/backend) + + @@ -186,4 +190,4 @@ const server = new ApolloServer({ }) ``` - \ No newline at end of file + diff --git a/v2/session/quick-setup/handling-session-tokens.mdx b/v2/session/quick-setup/handling-session-tokens.mdx index 5c8cb0294..bdfa1a21f 100644 --- a/v2/session/quick-setup/handling-session-tokens.mdx +++ b/v2/session/quick-setup/handling-session-tokens.mdx @@ -9,6 +9,7 @@ hide_title: true import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; @@ -23,6 +24,7 @@ There are two modes ways in which you can use sessions with SuperTokens: Our frontend SDK uses `httpOnly` cookie based session for websites by default as it secures against tokens theft via XSS attacks. For other platform like mobile apps, we use a bearer token in the Authorization header by default. This setting can be changed [as described in the token transfer section](../common-customizations/sessions/token-transfer-method) + ## If using our frontend SDK @@ -41,9 +43,9 @@ Our frontend SDK handles everything for you. You only need to make sure that you Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. -:::note By default, our web SDKs use cookies to provide credentials. -::: + + diff --git a/v2/session/serverless/with-aws-lambda/authorizer.mdx b/v2/session/serverless/with-aws-lambda/authorizer.mdx index 078cb159b..5f3688425 100644 --- a/v2/session/serverless/with-aws-lambda/authorizer.mdx +++ b/v2/session/serverless/with-aws-lambda/authorizer.mdx @@ -10,9 +10,12 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Using Lambda Authorizers + + You can use a lambda as an Authorizer in API Gateways. This will enable you to use SuperTokens in a lambda to authorize requests to other integrations (e.g., AppSync). An Authorizer pointed to this lambda will add `context.authorizer.principalId` that you can map to a header. For example, you can map this to an "x-user-id" header which will be set to the id of the logged-in user. If there is no valid session for the request, this header won't exist. ## 1) Add configurations and dependencies diff --git a/v2/session/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/session/serverless/with-aws-lambda/jwt-authorizer.mdx index 86055a1d9..14f128558 100644 --- a/v2/session/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/session/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -16,7 +16,14 @@ import TabItem from '@theme/TabItem'; # Using JWT Authorizers :::caution + AWS supports JWT authorizers for HTTP APIs and not REST APIs on the API Gateway service. For REST APIs follow the [Lambda authorizer](./authorizer) guide + +This guide will work if you are using **SuperTokens Session Tokens**. + +If you implementing an **OAuth2** setup, through the [**Unified Login**](/docs/unified-login/introduction) or the [**Microservice Authentication**](/docs/microservice_auth/client-credentials) features, you will have to manually set the token audience property. +Please check the referenced pages for more information. + ::: ## 1) Add the `aud` claim in the JWT based on the authorizer configuration diff --git a/v2/session/serverless/with-aws-lambda/session-verification.mdx b/v2/session/serverless/with-aws-lambda/session-verification.mdx index 02857b5e7..ae4954e93 100644 --- a/v2/session/serverless/with-aws-lambda/session-verification.mdx +++ b/v2/session/serverless/with-aws-lambda/session-verification.mdx @@ -9,9 +9,12 @@ hide_title: true import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 3. Session verification / Building your APIs + + When building your own APIs, you may need to verify the session of the user before proceeding further. SuperTokens SDK exposes a `verifySession` function that can be utilized for this. In this guide, we will be creating a `/user` `GET` route that will return the current session information. ## 1) Add `/user` `GET` route in your API Gateway diff --git a/v2/session/serverless/with-netlify/session-verification.mdx b/v2/session/serverless/with-netlify/session-verification.mdx index 1fd4b1a1f..b28b7cd62 100644 --- a/v2/session/serverless/with-netlify/session-verification.mdx +++ b/v2/session/serverless/with-netlify/session-verification.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # 4. Session verification / Building your APIs + + For this guide, we will assume that we want an API `/.netlify/functions/user GET` which returns the current session information. ## 1) Create a new file `netlify/functions/user.js` diff --git a/v2/src/components/OAuthDisclaimer.tsx b/v2/src/components/OAuthDisclaimer.tsx new file mode 100644 index 000000000..6fc0db67f --- /dev/null +++ b/v2/src/components/OAuthDisclaimer.tsx @@ -0,0 +1,96 @@ +import React from "react"; + +function OAuthVerifyTokensDisclaimer() { + return ( +
+
+
CAUTION
+
+
+

+ This guide only applies to scenarios which involve{" "} + SuperTokens Session Access Tokens. +

+

+ If you are implementing either,{" "} + + Unified Login + {" "} + or{" "} + + Microservice Authentication + + , features that make use of OAuth2 Access Tokens, + please check the{" "} + + separate page + {" "} + that shows you how to verify those types of tokens. +

+
+
+ ); +} + +function OAuthEmailVerificationDisclaimer() { + return ( +
+
+
CAUTION
+
+
+

+ This information only applies to scenarios in which you are using{" "} + SuperTokens Session Access Tokens. +

+

+ If you are implementing{" "} + + Unified Login + {" "} + you will have to manually check the email_verified claim + on the OAuth2 Access Tokens. Please read the{" "} + + separate page + {" "} + that shows you how to verify the token. +

+
+
+ ); +} + +function OAuthFrontendVerificationDisclaimer() { + return ( +
+
+
CAUTION
+
+
+

+ This information only applies to scenarios in which you are using{" "} + SuperTokens Session Access Tokens. +

+

+ If you are implementing{" "} + + Unified Login + {" "} + you will have to manually check authentication state based the{" "} + OAuth2/OIDC library that you are using. Please + explore the{" "} + + dedicated documentation + {" "} + to find out more. +

+
+
+ ); +} + +export { + OAuthVerifyTokensDisclaimer, + OAuthEmailVerificationDisclaimer, + OAuthFrontendVerificationDisclaimer, +}; diff --git a/v2/src/components/recipeBoxes/guides.json b/v2/src/components/recipeBoxes/guides.json index 2da4c4940..02a3f73b9 100644 --- a/v2/src/components/recipeBoxes/guides.json +++ b/v2/src/components/recipeBoxes/guides.json @@ -72,6 +72,11 @@ "title": "Attack Protection Suite", "icon": "attackprotectionsuite", "path": "/docs/attackprotectionsuite/introduction" + }, + { + "title": "Unified Login", + "icon": "unified-login", + "path": "/docs/unified-login/introduction" } ], "ref": [ @@ -86,4 +91,4 @@ "path": "/docs/community/sdks" } ] -} \ No newline at end of file +} diff --git a/v2/src/components/tabs/OAuthBackendTabs.tsx b/v2/src/components/tabs/OAuthBackendTabs.tsx new file mode 100644 index 000000000..5a77ea8ab --- /dev/null +++ b/v2/src/components/tabs/OAuthBackendTabs.tsx @@ -0,0 +1,22 @@ +import React from "react"; +let Tabs = require("@theme/Tabs").default; + +export default function OAuthBackendTabs(props: any) { + return ( + + {props.children} + + ); +} diff --git a/v2/src/components/tabs/OAuthFrontendTabs.tsx b/v2/src/components/tabs/OAuthFrontendTabs.tsx new file mode 100644 index 000000000..4fcc4b7a9 --- /dev/null +++ b/v2/src/components/tabs/OAuthFrontendTabs.tsx @@ -0,0 +1,19 @@ +import React from "react"; +let Tabs = require("@theme/Tabs").default; + +export default function OAuthFrontendTabs(props: any) { + return ( + + {props.children} + + ); +} diff --git a/v2/src/components/tabs/OAuthMobileTabs.tsx b/v2/src/components/tabs/OAuthMobileTabs.tsx new file mode 100644 index 000000000..00fb75f12 --- /dev/null +++ b/v2/src/components/tabs/OAuthMobileTabs.tsx @@ -0,0 +1,20 @@ +import React from "react"; +let Tabs = require("@theme/Tabs").default; + +export default function OAuthMobileTabs(props: any) { + return ( + + {props.children} + + ); +} diff --git a/v2/src/plugins/codeTypeChecking/csharpEnv/Dockerfile b/v2/src/plugins/codeTypeChecking/csharpEnv/Dockerfile new file mode 100644 index 000000000..eb57524a0 --- /dev/null +++ b/v2/src/plugins/codeTypeChecking/csharpEnv/Dockerfile @@ -0,0 +1,10 @@ +FROM mcr.microsoft.com/dotnet/sdk:6.0 + +WORKDIR /app + +COPY validate-csharp.sh . +COPY snippets ./snippets + +RUN chmod +x validate-csharp.sh + +ENTRYPOINT ["./validate-csharp.sh"] diff --git a/v2/src/plugins/codeTypeChecking/csharpEnv/snippets/.gitkeep b/v2/src/plugins/codeTypeChecking/csharpEnv/snippets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/v2/src/plugins/codeTypeChecking/csharpEnv/snippets/main.cs b/v2/src/plugins/codeTypeChecking/csharpEnv/snippets/main.cs new file mode 100644 index 000000000..99da56a03 --- /dev/null +++ b/v2/src/plugins/codeTypeChecking/csharpEnv/snippets/main.cs @@ -0,0 +1,7 @@ +public class MainProgram +{ + public static void Main(string[] args) + { + System.Console.WriteLine("Hello, World!"); + } +} diff --git a/v2/src/plugins/codeTypeChecking/csharpEnv/snippets/snippets.csproj b/v2/src/plugins/codeTypeChecking/csharpEnv/snippets/snippets.csproj new file mode 100644 index 000000000..d2f870c47 --- /dev/null +++ b/v2/src/plugins/codeTypeChecking/csharpEnv/snippets/snippets.csproj @@ -0,0 +1,11 @@ + + + Exe + net6.0 + + + + + + + diff --git a/v2/src/plugins/codeTypeChecking/csharpEnv/validate-csharp.sh b/v2/src/plugins/codeTypeChecking/csharpEnv/validate-csharp.sh new file mode 100644 index 000000000..e9687730c --- /dev/null +++ b/v2/src/plugins/codeTypeChecking/csharpEnv/validate-csharp.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +cd "snippets" + +dotnet build + +if [ $? -eq 0 ]; then + echo "C# code is valid" + exit 0 +else + echo "C# code is invalid" + exit 1 +fi diff --git a/v2/src/plugins/codeTypeChecking/goEnv/go.mod b/v2/src/plugins/codeTypeChecking/goEnv/go.mod index 2d12c54f6..fe78afa2d 100644 --- a/v2/src/plugins/codeTypeChecking/goEnv/go.mod +++ b/v2/src/plugins/codeTypeChecking/goEnv/go.mod @@ -12,22 +12,31 @@ require ( github.com/supertokens/supertokens-golang v0.25.0 ) +require github.com/lestrrat-go/jwx v1.2.30 + require ( github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/derekstavis/go-qs v0.0.0-20180720192143-9eef69e6c4e7 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.11.1 // indirect - github.com/goccy/go-json v0.9.11 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -35,13 +44,13 @@ require ( github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.8.1 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/twilio/twilio-go v0.26.0 // indirect github.com/ugorji/go/codec v1.2.7 // indirect - golang.org/x/crypto v0.2.0 // indirect - golang.org/x/net v0.4.0 // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/text v0.5.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect diff --git a/v2/src/plugins/codeTypeChecking/goEnv/go.sum b/v2/src/plugins/codeTypeChecking/goEnv/go.sum index e87a83529..cc906c0e1 100644 --- a/v2/src/plugins/codeTypeChecking/goEnv/go.sum +++ b/v2/src/plugins/codeTypeChecking/goEnv/go.sum @@ -4,6 +4,9 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/derekstavis/go-qs v0.0.0-20180720192143-9eef69e6c4e7 h1:zmAiXR9h1TCVN/0yCMRYQNE91dNRORpSzMFiqfTTPOs= github.com/derekstavis/go-qs v0.0.0-20180720192143-9eef69e6c4e7/go.mod h1:Vgz4nKcG6+B7QcALsWZpmhyQTLSl7nwFGKSrbq2LxEo= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= @@ -29,8 +32,9 @@ github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXS github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= @@ -40,8 +44,9 @@ github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+Licev github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= @@ -62,6 +67,19 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.30 h1:VKIFrmjYn0z2J51iLPadqoHIVLzvWNa1kCsTqNDHYPA= +github.com/lestrrat-go/jwx v1.2.30/go.mod h1:vMxrwFhunGZ3qddmfmEm2+uced8MSI6QFWGTKygjSzQ= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -88,13 +106,16 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supertokens/supertokens-golang v0.25.0 h1:yTWBKD8tZFe6sYSQ5h1IYTsH/c3UQlqyqRvSFGVXx/o= github.com/supertokens/supertokens-golang v0.25.0/go.mod h1:/n6zQ9461RscnnWB4Y4bWwzhPivnj8w79j/doqkLOs8= github.com/twilio/twilio-go v0.26.0 h1:wFW4oTe3/LKt6bvByP7eio8JsjtaLHjMQKOUEzQry7U= @@ -110,10 +131,18 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE= golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -121,11 +150,20 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -139,27 +177,48 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/v2/src/plugins/codeTypeChecking/index.js b/v2/src/plugins/codeTypeChecking/index.js index 1cc0fbf77..09feaec9f 100644 --- a/v2/src/plugins/codeTypeChecking/index.js +++ b/v2/src/plugins/codeTypeChecking/index.js @@ -1,10 +1,10 @@ -let fs = require('fs'); -let readdir = require("fs").promises.readdir -let path = require('path'); -var exec = require('child_process').exec; -var crypto = require('crypto'); +let fs = require("fs"); +let readdir = require("fs").promises.readdir; +let path = require("path"); +var exec = require("child_process").exec; +var crypto = require("crypto"); let mdVars = require("../markdownVariables.json"); -const { execSync } = require('child_process'); +const { execSync } = require("child_process"); let defaultMainContent = ` @@ -16,689 +16,1128 @@ void main() { let mainContent = defaultMainContent; function hash(input) { - return crypto.createHash('md5').update(input).digest('hex'); + return crypto.createHash("md5").update(input).digest("hex"); } async function addCodeSnippetToEnv(mdFile, isSwiftEnabled) { - if (mdFile.includes("/v2/change_me/") || mdFile.includes("/v2/contribute/") || - mdFile.includes("/v2/nodejs") || mdFile.includes("/v2/golang") || mdFile.includes("/v2/python") || - mdFile.includes("/v2/auth-react") || mdFile.includes("/v2/website") || mdFile.includes("/v2/react-native") || mdFile.includes("/codeTypeChecking/")) { - return; - } - return new Promise((res, rej) => { - fs.readFile(mdFile, 'utf8', async (err, data) => { - if (err) { - return rej(err); + if ( + mdFile.includes("/v2/change_me/") || + mdFile.includes("/v2/contribute/") || + mdFile.includes("/v2/nodejs") || + mdFile.includes("/v2/golang") || + mdFile.includes("/v2/python") || + mdFile.includes("/v2/auth-react") || + mdFile.includes("/v2/website") || + mdFile.includes("/v2/react-native") || + mdFile.includes("/codeTypeChecking/") + ) { + return; + } + return new Promise((res, rej) => { + fs.readFile(mdFile, "utf8", async (err, data) => { + if (err) { + return rej(err); + } + let fileNameCounter = 0; + let originalData = data; + + let lines = originalData.split("\n"); + + let currentCodeSnippet = ""; + let currentCodeLanguage = ""; + let startAppendingToCodeSnippet = false; + for (let i = 0; i < lines.length; i++) { + let currLineTrimmed = lines[i].trim(); + if (startAppendingToCodeSnippet && !currLineTrimmed.startsWith("```")) { + currentCodeSnippet = currentCodeSnippet + "\n" + lines[i]; + } + if (currLineTrimmed.startsWith("```")) { + startAppendingToCodeSnippet = !startAppendingToCodeSnippet; + if (!startAppendingToCodeSnippet) { + if (currLineTrimmed !== "```") { + return rej( + new Error( + `Something wrong in how a code snippet has ended in ${mdFile}`, + ), + ); } - let fileNameCounter = 0; - let originalData = data; - - let lines = originalData.split("\n"); - - let currentCodeSnippet = ""; - let currentCodeLanguage = ""; - let startAppendingToCodeSnippet = false; - for (let i = 0; i < lines.length; i++) { - let currLineTrimmed = lines[i].trim(); - if (startAppendingToCodeSnippet && !currLineTrimmed.startsWith("```")) { - currentCodeSnippet = currentCodeSnippet + "\n" + lines[i]; - } - if (currLineTrimmed.startsWith("```")) { - startAppendingToCodeSnippet = !startAppendingToCodeSnippet; - if (!startAppendingToCodeSnippet) { - if (currLineTrimmed !== "```") { - return rej(new Error(`Something wrong in how a code snippet has ended in ${mdFile}`)); - } - // we just finished copying a code snippet - if (currentCodeLanguage !== "ignore") { - await addCodeSnippetToEnvHelper(currentCodeSnippet, currentCodeLanguage, mdFile, fileNameCounter); - fileNameCounter++; - } - currentCodeSnippet = ""; - currentCodeLanguage = ""; - } else { - // we are starting a code block - if (currLineTrimmed === "```js" || currLineTrimmed.startsWith("```js ") || - currLineTrimmed === "```jsx" || currLineTrimmed.startsWith("```jsx ")) { - return rej(new Error(`Please do not use js or jsx in code snippets. Only ts or tsx. Error in ` + mdFile)); - } else if (currLineTrimmed === "```ts" || currLineTrimmed.startsWith("```ts ") || - currLineTrimmed === "```tsx" || currLineTrimmed.startsWith("```tsx ")) { - currentCodeLanguage = "typescript"; - } else if (currLineTrimmed === "```go" || currLineTrimmed.startsWith("```go ")) { - currentCodeLanguage = "go"; - } else if (currLineTrimmed === "```python" || currLineTrimmed.startsWith("```python ")) { - currentCodeLanguage = "python"; - } else if (currLineTrimmed === "```kotlin" || currLineTrimmed.startsWith("```kotlin ")) { - currentCodeLanguage = "kotlin"; - } else if (currLineTrimmed === "```swift" || currLineTrimmed.startsWith("```swift ")) { - currentCodeLanguage = isSwiftEnabled ? "swift" : "ignore" - } else if (currLineTrimmed.includes("bash") || currLineTrimmed.includes("yaml") || currLineTrimmed.includes("cql") || currLineTrimmed.includes("sql") || currLineTrimmed.includes("batch") || - currLineTrimmed.includes("text") || currLineTrimmed.includes("json") - || currLineTrimmed.includes("html")) { - currentCodeLanguage = "ignore" - } else if (currLineTrimmed === "```dart" || currLineTrimmed.startsWith("```dart ")) { - currentCodeLanguage = "dart"; - } else { - return rej(new Error(`UNABLE TO RECOGNISE LANGUAGE ${currLineTrimmed} in file ${mdFile}.`)); - } - } - } + // we just finished copying a code snippet + if (currentCodeLanguage !== "ignore") { + await addCodeSnippetToEnvHelper( + currentCodeSnippet, + currentCodeLanguage, + mdFile, + fileNameCounter, + ); + fileNameCounter++; } - res(); - }); + currentCodeSnippet = ""; + currentCodeLanguage = ""; + } else { + // we are starting a code block + if ( + currLineTrimmed === "```js" || + currLineTrimmed.startsWith("```js ") || + currLineTrimmed === "```jsx" || + currLineTrimmed.startsWith("```jsx ") + ) { + return rej( + new Error( + `Please do not use js or jsx in code snippets. Only ts or tsx. Error in ` + + mdFile, + ), + ); + } else if ( + currLineTrimmed === "```ts" || + currLineTrimmed.startsWith("```ts ") || + currLineTrimmed === "```tsx" || + currLineTrimmed.startsWith("```tsx ") + ) { + currentCodeLanguage = "typescript"; + } else if ( + currLineTrimmed === "```go" || + currLineTrimmed.startsWith("```go ") + ) { + currentCodeLanguage = "go"; + } else if ( + currLineTrimmed === "```python" || + currLineTrimmed.startsWith("```python ") + ) { + currentCodeLanguage = "python"; + } else if ( + currLineTrimmed === "```kotlin" || + currLineTrimmed.startsWith("```kotlin ") + ) { + currentCodeLanguage = "kotlin"; + } else if ( + currLineTrimmed === "```php" || + currLineTrimmed.startsWith("```php ") + ) { + currentCodeLanguage = "php"; + } else if ( + currLineTrimmed === "```java" || + currLineTrimmed.startsWith("```java ") + ) { + currentCodeLanguage = "java"; + } else if ( + currLineTrimmed === "```csharp" || + currLineTrimmed.startsWith("```csharp ") + ) { + currentCodeLanguage = "csharp"; + } else if ( + currLineTrimmed === "```swift" || + currLineTrimmed.startsWith("```swift ") + ) { + currentCodeLanguage = isSwiftEnabled ? "swift" : "ignore"; + } else if ( + currLineTrimmed.includes("bash") || + currLineTrimmed.includes("yaml") || + currLineTrimmed.includes("cql") || + currLineTrimmed.includes("sql") || + currLineTrimmed.includes("batch") || + currLineTrimmed.includes("text") || + currLineTrimmed.includes("json") || + currLineTrimmed.includes("html") + ) { + currentCodeLanguage = "ignore"; + } else if ( + currLineTrimmed === "```dart" || + currLineTrimmed.startsWith("```dart ") + ) { + currentCodeLanguage = "dart"; + } else { + return rej( + new Error( + `UNABLE TO RECOGNISE LANGUAGE ${currLineTrimmed} in file ${mdFile}.`, + ), + ); + } + } + } + } + res(); }); + }); } async function getFiles(dir) { - if (!fs.existsSync(dir)) { - return []; - } - const dirents = await readdir(dir, { withFileTypes: true }); - const files = await Promise.all(dirents.map((dirent) => { - const res = path.resolve(dir, dirent.name); - return dirent.isDirectory() ? getFiles(res) : res; - })); - return Array.prototype.concat(...files); + if (!fs.existsSync(dir)) { + return []; + } + const dirents = await readdir(dir, { withFileTypes: true }); + const files = await Promise.all( + dirents.map((dirent) => { + const res = path.resolve(dir, dirent.name); + return dirent.isDirectory() ? getFiles(res) : res; + }), + ); + return Array.prototype.concat(...files); } function cleanEmptyFoldersRecursively(folder) { - if (!fs.existsSync(folder)) { - return; - } - var isDir = fs.statSync(folder).isDirectory(); - if (!isDir) { - return; - } - var files = fs.readdirSync(folder); - if (files.length > 0) { - files.forEach(function (file) { - var fullPath = path.join(folder, file); - cleanEmptyFoldersRecursively(fullPath); - }); - - // re-evaluate files; after deleting subfolder - // we may have parent folder empty now - files = fs.readdirSync(folder); - } + if (!fs.existsSync(folder)) { + return; + } + var isDir = fs.statSync(folder).isDirectory(); + if (!isDir) { + return; + } + var files = fs.readdirSync(folder); + if (files.length > 0) { + files.forEach(function (file) { + var fullPath = path.join(folder, file); + cleanEmptyFoldersRecursively(fullPath); + }); - if (files.length == 0) { - fs.rmdirSync(folder); - return; - } + // re-evaluate files; after deleting subfolder + // we may have parent folder empty now + files = fs.readdirSync(folder); + } + + if (files.length == 0) { + fs.rmdirSync(folder); + return; + } } async function deleteFilesWithoutErrorsTypescript(stdout) { - let errors = stdout.split("\n"); - let fileNames = []; - - /** - * NOTE that because of how typescript reports file paths, all paths in - * fileNames will start from /snippets and not from the root (~/) - */ - errors.forEach(error => { - // tsc output has some other lines + info that we are not interested in - if (error !== "" && error.includes("snippets")) { - fileNames.push(error.split("(")[0]); - } - }) + let errors = stdout.split("\n"); + let fileNames = []; + + /** + * NOTE that because of how typescript reports file paths, all paths in + * fileNames will start from /snippets and not from the root (~/) + */ + errors.forEach((error) => { + // tsc output has some other lines + info that we are not interested in + if (error !== "" && error.includes("snippets")) { + fileNames.push(error.split("(")[0]); + } + }); - let snippetsPathPrefix = "src/plugins/codeTypeChecking/jsEnv/snippets/"; - let files = await getFiles(snippetsPathPrefix); - await getRootDir(); + let snippetsPathPrefix = "src/plugins/codeTypeChecking/jsEnv/snippets/"; + let files = await getFiles(snippetsPathPrefix); + await getRootDir(); - files.forEach(file => { - let actualPath = file.replace(rootDir, ""); + files.forEach((file) => { + let actualPath = file.replace(rootDir, ""); - // We replace the path from project root to snippets with just snippets/ to match the format that tsc outputs - if (!fileNames.includes(actualPath.replace(snippetsPathPrefix, "snippets/"))) { - let exists = fs.existsSync(actualPath); - if (exists) { - fs.rmSync(actualPath); - } - } - }); + // We replace the path from project root to snippets with just snippets/ to match the format that tsc outputs + if ( + !fileNames.includes(actualPath.replace(snippetsPathPrefix, "snippets/")) + ) { + let exists = fs.existsSync(actualPath); + if (exists) { + fs.rmSync(actualPath); + } + } + }); - cleanEmptyFoldersRecursively("src/plugins/codeTypeChecking/jsEnv/snippets"); + cleanEmptyFoldersRecursively("src/plugins/codeTypeChecking/jsEnv/snippets"); } - async function deleteFilesWithoutErrorsPython(stdout) { - let errors = stdout.split("\n"); - let fileNames = []; + let errors = stdout.split("\n"); + let fileNames = []; - errors.forEach(error => { - if (error !== "" && error.includes("snippets")) { - fileNames.push(error.split(":")[0].trim()); - } - }) + errors.forEach((error) => { + if (error !== "" && error.includes("snippets")) { + fileNames.push(error.split(":")[0].trim()); + } + }); - let snippetsPathPrefix = "src/plugins/codeTypeChecking/pythonEnv/snippets/"; - let files = await getFiles(snippetsPathPrefix); - await getRootDir(); + let snippetsPathPrefix = "src/plugins/codeTypeChecking/pythonEnv/snippets/"; + let files = await getFiles(snippetsPathPrefix); + await getRootDir(); - files.forEach(file => { - if (!fileNames.includes(file)) { - let exists = fs.existsSync(file); - if (exists) { - fs.rmSync(file); - } - } - }); + files.forEach((file) => { + if (!fileNames.includes(file)) { + let exists = fs.existsSync(file); + if (exists) { + fs.rmSync(file); + } + } + }); - cleanEmptyFoldersRecursively("src/plugins/codeTypeChecking/pythonEnv/snippets"); + cleanEmptyFoldersRecursively( + "src/plugins/codeTypeChecking/pythonEnv/snippets", + ); } async function checkCodeSnippets(language) { - // typescript.. - if (language === "typescript") { - await new Promise((res, rej) => { - exec("cd src/plugins/codeTypeChecking/jsEnv/ && npm run test", async function (err, stdout, stderr) { - await deleteFilesWithoutErrorsTypescript(stdout); - if (err) { - console.log('\x1b[31m%s\x1b[0m', stdout); - console.log('\x1b[31m%s\x1b[0m', err); - console.log("=======SETUP INSTRS========\n"); - console.log('\x1b[36m%s\x1b[0m', `To setup a JS env, run the following (from v2 folder): + // typescript.. + if (language === "typescript") { + await new Promise((res, rej) => { + exec( + "cd src/plugins/codeTypeChecking/jsEnv/ && npm run test", + async function (err, stdout, stderr) { + await deleteFilesWithoutErrorsTypescript(stdout); + if (err) { + console.log("\x1b[31m%s\x1b[0m", stdout); + console.log("\x1b[31m%s\x1b[0m", err); + console.log("=======SETUP INSTRS========\n"); + console.log( + "\x1b[36m%s\x1b[0m", + `To setup a JS env, run the following (from v2 folder): - cd src/plugins/codeTypeChecking/jsEnv/ - npm i - - npm run test (to make sure that it's setup correctly)`) - console.log("==========================\n"); + - npm run test (to make sure that it's setup correctly)`, + ); + console.log("==========================\n"); - return rej(err); - } - res(); - }); - }) - } else if (language === "go") { - await new Promise((res, rej) => { - exec("cd src/plugins/codeTypeChecking/goEnv/ && go mod tidy && go build ./...", function (err, stdout, stderr) { - if (err) { - console.log('\x1b[31m%s\x1b[0m', stdout); - console.log('\x1b[31m%s\x1b[0m', err); - console.log("=======SETUP INSTRS========\n"); - console.log('\x1b[36m%s\x1b[0m', `Make sure that you have go installed on your system and try this command again`) - console.log("==========================\n"); - return rej(err); - } - res(); - }); - }) - } else if (language === "python") { - await new Promise((res, rej) => { - exec("cd src/plugins/codeTypeChecking/pythonEnv/ && ./checkSnippets.sh", async function (err, stdout, stderr) { - await deleteFilesWithoutErrorsPython(stdout); - if (err) { - console.log('\x1b[31m%s\x1b[0m', stdout); - console.log('\x1b[31m%s\x1b[0m', err); - console.log("=======SETUP INSTRS========\n"); - console.log('\x1b[36m%s\x1b[0m', `To setup a python env, run the following (from v2 folder): + return rej(err); + } + res(); + }, + ); + }); + } else if (language === "go") { + await new Promise((res, rej) => { + exec( + "cd src/plugins/codeTypeChecking/goEnv/ && go mod tidy && go build ./...", + function (err, stdout, stderr) { + if (err) { + console.log("\x1b[31m%s\x1b[0m", stdout); + console.log("\x1b[31m%s\x1b[0m", err); + console.log("=======SETUP INSTRS========\n"); + console.log( + "\x1b[36m%s\x1b[0m", + `Make sure that you have go installed on your system and try this command again`, + ); + console.log("==========================\n"); + return rej(err); + } + res(); + }, + ); + }); + } else if (language === "python") { + await new Promise((res, rej) => { + exec( + "cd src/plugins/codeTypeChecking/pythonEnv/ && ./checkSnippets.sh", + async function (err, stdout, stderr) { + await deleteFilesWithoutErrorsPython(stdout); + if (err) { + console.log("\x1b[31m%s\x1b[0m", stdout); + console.log("\x1b[31m%s\x1b[0m", err); + console.log("=======SETUP INSTRS========\n"); + console.log( + "\x1b[36m%s\x1b[0m", + `To setup a python env, run the following (from v2 folder): - cd src/plugins/codeTypeChecking/pythonEnv/ - virtualenv ./venv - source venv/bin/activate - pip install -r requirements.txt - - pylint ./snippets (to make sure that it's setup correctly)`) - console.log("==========================\n"); - return rej(err); - } - res(); - }); - }) - } else if (language === "kotlin") { - await new Promise((res, rej) => { - exec("cd src/plugins/codeTypeChecking/kotlinEnv/ && ./gradlew build", async function (err, stdout, stderr) { - if (err) { - console.log('\x1b[31m%s\x1b[0m', stdout); - console.log('\x1b[31m%s\x1b[0m', err); - console.log("=======SETUP INSTRS========\n"); - console.log('\x1b[36m%s\x1b[0m', `Make sure that you have kotlin installed on your system and try this command again`) - console.log("==========================\n"); - return rej(err); - } - res(); - }); - }) - } else if (language === "swift") { - await new Promise((res, rej) => { - exec("cd src/plugins/codeTypeChecking/iosenv/ && set -o pipefail && xcodebuild -workspace iosenv.xcworkspace -scheme iosenv build CODE_SIGN_IDENTITY='' CODE_SIGNING_REQUIRED=NO -quiet | ./Pods/xcbeautify/xcbeautify", async function (err, stdout, stderr) { - if (err) { - console.log('\x1b[31m%s\x1b[0m', stdout); - console.log('\x1b[31m%s\x1b[0m', err); - console.log("=======SETUP INSTRS========\n"); - console.log('\x1b[36m%s\x1b[0m', `Make sure that you have Xcode installed on your system and try this command again`) - console.log('\x1b[36m%s\x1b[0m', `Make sure that you have run 'pod install' inside iosenv and try this command again`) - console.log("==========================\n"); - return rej(err); - } - res(); - }); - }) - } else if (language === "dart") { - await new Promise((res, rej) => { - exec("cd src/plugins/codeTypeChecking/dart_env/ && flutter build web -t lib/snippets/main.dart", async function (err, stdout, stderr) { - if (err) { - console.log('\x1b[31m%s\x1b[0m', stdout); - console.log('\x1b[31m%s\x1b[0m', err); - console.log("=======SETUP INSTRS========\n"); - console.log('\x1b[36m%s\x1b[0m', `Make sure that you have flutter setup on your system and try this command again`) - console.log('\x1b[36m%s\x1b[0m', `Make sure that you have run 'flutter pub get' inside dart_env and try this command again`) - console.log("==========================\n"); - return rej(err); - } - res(); - }); - }) - } else { - throw new Error("Unsupported language in checkCodeSnippets"); - } + - pylint ./snippets (to make sure that it's setup correctly)`, + ); + console.log("==========================\n"); + return rej(err); + } + res(); + }, + ); + }); + } else if (language === "kotlin") { + await new Promise((res, rej) => { + exec( + "cd src/plugins/codeTypeChecking/kotlinEnv/ && ./gradlew build", + async function (err, stdout, stderr) { + if (err) { + console.log("\x1b[31m%s\x1b[0m", stdout); + console.log("\x1b[31m%s\x1b[0m", err); + console.log("=======SETUP INSTRS========\n"); + console.log( + "\x1b[36m%s\x1b[0m", + `Make sure that you have kotlin installed on your system and try this command again`, + ); + console.log("==========================\n"); + return rej(err); + } + res(); + }, + ); + }); + } else if (language === "swift") { + await new Promise((res, rej) => { + exec( + "cd src/plugins/codeTypeChecking/iosenv/ && set -o pipefail && xcodebuild -workspace iosenv.xcworkspace -scheme iosenv build CODE_SIGN_IDENTITY='' CODE_SIGNING_REQUIRED=NO -quiet | ./Pods/xcbeautify/xcbeautify", + async function (err, stdout, stderr) { + if (err) { + console.log("\x1b[31m%s\x1b[0m", stdout); + console.log("\x1b[31m%s\x1b[0m", err); + console.log("=======SETUP INSTRS========\n"); + console.log( + "\x1b[36m%s\x1b[0m", + `Make sure that you have Xcode installed on your system and try this command again`, + ); + console.log( + "\x1b[36m%s\x1b[0m", + `Make sure that you have run 'pod install' inside iosenv and try this command again`, + ); + console.log("==========================\n"); + return rej(err); + } + res(); + }, + ); + }); + } else if (language === "dart") { + await new Promise((res, rej) => { + exec( + "cd src/plugins/codeTypeChecking/dart_env/ && flutter build web -t lib/snippets/main.dart", + async function (err, stdout, stderr) { + if (err) { + console.log("\x1b[31m%s\x1b[0m", stdout); + console.log("\x1b[31m%s\x1b[0m", err); + console.log("=======SETUP INSTRS========\n"); + console.log( + "\x1b[36m%s\x1b[0m", + `Make sure that you have flutter setup on your system and try this command again`, + ); + console.log( + "\x1b[36m%s\x1b[0m", + `Make sure that you have run 'flutter pub get' inside dart_env and try this command again`, + ); + console.log("==========================\n"); + return rej(err); + } + res(); + }, + ); + }); + } else if (language === "php") { + await new Promise((res, rej) => { + const buildCommand = `docker build -t supertokens/php-env-type-checking .`; + const runCommand = `docker run supertokens/php-env-type-checking`; + + exec( + `cd src/plugins/codeTypeChecking/phpEnv/ && ${buildCommand} && ${runCommand}`, + async function (err, stdout, stderr) { + if (err) { + console.log("\x1b[31m%s\x1b[0m", stdout); + console.log("\x1b[31m%s\x1b[0m", err); + console.log("=======SETUP INSTRS========\n"); + console.log( + "\x1b[36m%s\x1b[0m", + `Make sure that you have docker installed`, + ); + console.log( + "\x1b[36m%s\x1b[0m", + `Run ${buildCommand} and ${runCommand} inside phpEnv`, + ); + console.log("==========================\n"); + return rej(err); + } + res(); + }, + ); + }); + } else if (language === "java") { + await new Promise((res, rej) => { + // const buildCommand = `docker build -t supertokens/java-env-type-checking .`; + // const runCommand = `docker run supertokens/java-env-type-checking`; + const validateCommand = `cd src/plugins/codeTypeChecking/javaEnv/ && chmod +x ./validate-java.sh && ./validate-java.sh`; + exec(`${validateCommand}`, async function (err, stdout, stderr) { + if (err) { + console.log("\x1b[31m%s\x1b[0m", stdout); + console.log("\x1b[31m%s\x1b[0m", err); + console.log("=======SETUP INSTRS========\n"); + console.log( + "\x1b[36m%s\x1b[0m", + `Make sure that you have docker installed`, + ); + console.log("\x1b[36m%s\x1b[0m", `Run ${validateCommand}`); + console.log("==========================\n"); + return rej(err); + } + res(); + }); + }); + } else if (language === "csharp") { + await new Promise((res, rej) => { + // const buildCommand = `docker build -t supertokens/csharp-env-type-checking .`; + // const runCommand = `docker run supertokens/csharp-env-type-checking`; + const validateCommand = `cd src/plugins/codeTypeChecking/csharpEnv/ && chmod +x ./validate-csharp.sh && ./validate-csharp.sh`; + exec(`${validateCommand}`, async function (err, stdout, stderr) { + if (err) { + console.log("\x1b[31m%s\x1b[0m", stdout); + console.log("\x1b[31m%s\x1b[0m", err); + console.log("=======SETUP INSTRS========\n"); + console.log( + "\x1b[36m%s\x1b[0m", + `Make sure that you have docker installed`, + ); + console.log("\x1b[36m%s\x1b[0m", `Run ${validateCommand}`); + console.log("==========================\n"); + return rej(err); + } + res(); + }); + }); + } else { + throw new Error("Unsupported language in checkCodeSnippets"); + } } let rootDir = undefined; // uptil v2 async function getRootDir() { - if (rootDir === undefined) { - rootDir = await new Promise(res => exec("pwd", function (err, stdout, stderr) { - res(stdout); - })); - rootDir = rootDir.trim() + "/"; - // at this point, rootDir is /Users/.../main-website/docs/v2/ or if only cloned docs, then its /Users/.../docs/v2/ - } + if (rootDir === undefined) { + rootDir = await new Promise((res) => + exec("pwd", function (err, stdout, stderr) { + res(stdout); + }), + ); + rootDir = rootDir.trim() + "/"; + // at this point, rootDir is /Users/.../main-website/docs/v2/ or if only cloned docs, then its /Users/.../docs/v2/ + } - return rootDir; + return rootDir; } async function getRecipeName(mdFile) { - if (rootDir === undefined) { - rootDir = await new Promise(res => exec("pwd", function (err, stdout, stderr) { - res(stdout); - })); - rootDir = rootDir.trim() + "/"; - // at this point, rootDir is /Users/.../main-website/docs/v2/ or if only cloned docs, then its /Users/.../docs/v2/ - } - let postV2 = mdFile.replace(rootDir, ""); - return postV2.split("/")[0]; + if (rootDir === undefined) { + rootDir = await new Promise((res) => + exec("pwd", function (err, stdout, stderr) { + res(stdout); + }), + ); + rootDir = rootDir.trim() + "/"; + // at this point, rootDir is /Users/.../main-website/docs/v2/ or if only cloned docs, then its /Users/.../docs/v2/ + } + let postV2 = mdFile.replace(rootDir, ""); + return postV2.split("/")[0]; } -async function addCodeSnippetToEnvHelper(codeSnippet, language, mdFile, codeBlockCountInFile) { - // we replace all the variables here so that the code can compile: - - // Excluding __OMIT_START__ and __OMIT_END__ tags from the code snippet before running the type check plugin - // We need to do this before intializing ogCodeSnippet to prevent false warnings about not using docs variables - codeSnippet = codeSnippet.replace(/__OMIT_START__([\s\S]*?)__OMIT_END__/g, '$1'); - - let ogCodeSnippet = codeSnippet; - codeSnippet = codeSnippet.replaceAll("^{coreInjector_connection_uri_comment}", ""); - codeSnippet = codeSnippet.replaceAll("^{coreInjector_uri}", "\"\","); - codeSnippet = codeSnippet.replaceAll("^{coreInjector_api_key_commented}", ""); - codeSnippet = codeSnippet.replaceAll("^{coreInjector_api_key}", "\"\""); - codeSnippet = codeSnippet.replaceAll("^{coreInjector_connection_uri_comment_with_hash}", "") - codeSnippet = codeSnippet.replaceAll("^{coreInjector_api_key_commented_with_hash}", "") - - codeSnippet = codeSnippet.replaceAll("^{form_flowType}", "USER_INPUT_CODE_AND_MAGIC_LINK"); - codeSnippet = codeSnippet.replaceAll("^{form_contactMethod}", "PHONE"); - codeSnippet = codeSnippet.replaceAll("^{form_contactMethod_initialize_Python}", "ContactPhoneOnlyConfig"); - codeSnippet = codeSnippet.replaceAll("^{form_contactMethod_import_Python}", "from supertokens_python.recipe.passwordless import ContactPhoneOnlyConfig"); - codeSnippet = codeSnippet.replaceAll("^{form_contactMethod_sendCB_Go}", - `ContactMethodPhone: plessmodels.ContactMethodPhoneConfig{ +async function addCodeSnippetToEnvHelper( + codeSnippet, + language, + mdFile, + codeBlockCountInFile, +) { + // we replace all the variables here so that the code can compile: + + // Excluding __OMIT_START__ and __OMIT_END__ tags from the code snippet before running the type check plugin + // We need to do this before intializing ogCodeSnippet to prevent false warnings about not using docs variables + codeSnippet = codeSnippet.replace( + /__OMIT_START__([\s\S]*?)__OMIT_END__/g, + "$1", + ); + + let ogCodeSnippet = codeSnippet; + codeSnippet = codeSnippet.replaceAll( + "^{coreInjector_connection_uri_comment}", + "", + ); + codeSnippet = codeSnippet.replaceAll("^{coreInjector_uri}", '"",'); + codeSnippet = codeSnippet.replaceAll("^{coreInjector_api_key_commented}", ""); + codeSnippet = codeSnippet.replaceAll("^{coreInjector_api_key}", '""'); + codeSnippet = codeSnippet.replaceAll( + "^{coreInjector_connection_uri_comment_with_hash}", + "", + ); + codeSnippet = codeSnippet.replaceAll( + "^{coreInjector_api_key_commented_with_hash}", + "", + ); + + codeSnippet = codeSnippet.replaceAll( + "^{form_flowType}", + "USER_INPUT_CODE_AND_MAGIC_LINK", + ); + codeSnippet = codeSnippet.replaceAll("^{form_contactMethod}", "PHONE"); + codeSnippet = codeSnippet.replaceAll( + "^{form_contactMethod_initialize_Python}", + "ContactPhoneOnlyConfig", + ); + codeSnippet = codeSnippet.replaceAll( + "^{form_contactMethod_import_Python}", + "from supertokens_python.recipe.passwordless import ContactPhoneOnlyConfig", + ); + codeSnippet = codeSnippet.replaceAll( + "^{form_contactMethod_sendCB_Go}", + `ContactMethodPhone: plessmodels.ContactMethodPhoneConfig{ Enabled: true, -},`); - - let recipeName = await getRecipeName(mdFile); - let replaceMap = { ...mdVars["common"], ...mdVars[recipeName] }; - if (replaceMap !== undefined) { - let keys = Object.keys(replaceMap); - for (let i = 0; i < keys.length; i++) { - if (codeSnippet.includes(`^{${keys[i]}}`)) { - codeSnippet = codeSnippet.replaceAll(`^{${keys[i]}}`, replaceMap[keys[i]]); - } - } - } +},`, + ); - // this block is there so that the dev knows that the resulting block should use variable code snippets. - if (ogCodeSnippet !== codeSnippet) { - for (let i = 0; i < 50; i++) { - if (language === "typescript" || language === "go" || language === "kotlin" || language === "swift" || language === "dart") { - codeSnippet = "// THIS FILE CONTAINS DOCS VARIABLES. PLEASE DO NOT FORGET TO USE THOSE\n" + codeSnippet; - } else if (language === "python") { - codeSnippet = "# THIS FILE CONTAINS DOCS VARIABLES. PLEASE DO NOT FORGET TO USE THOSE\n" + codeSnippet; - } else { - throw new Error("language not supported"); - } - } + let recipeName = await getRecipeName(mdFile); + let replaceMap = { ...mdVars["common"], ...mdVars[recipeName] }; + if (replaceMap !== undefined) { + let keys = Object.keys(replaceMap); + for (let i = 0; i < keys.length; i++) { + if (codeSnippet.includes(`^{${keys[i]}}`)) { + codeSnippet = codeSnippet.replaceAll( + `^{${keys[i]}}`, + replaceMap[keys[i]], + ); + } } + } + // this block is there so that the dev knows that the resulting block should use variable code snippets. + if (ogCodeSnippet !== codeSnippet) { + for (let i = 0; i < 50; i++) { + if ( + language === "typescript" || + language === "go" || + language === "kotlin" || + language === "swift" || + language === "dart" + ) { + codeSnippet = + "// THIS FILE CONTAINS DOCS VARIABLES. PLEASE DO NOT FORGET TO USE THOSE\n" + + codeSnippet; + } else if (language === "python") { + codeSnippet = + "# THIS FILE CONTAINS DOCS VARIABLES. PLEASE DO NOT FORGET TO USE THOSE\n" + + codeSnippet; + } else { + throw new Error("language not supported"); + } + } + } - if (language === "typescript") { - if (codeSnippet.includes("require(")) { - // except for the attack protection suite , where we need to allow require for compatibility reasons, - // as the SDK URL is dynamic and import might break some builds - if (!codeSnippet.includes('require("https://deviceid.supertokens.io')) { - throw new Error("Do not use 'require' in TS code. Error in " + mdFile); - } - } - codeSnippet = `export { }\n// Original: ${mdFile}\n${codeSnippet}`; // see https://www.aritsltd.com/blog/frontend-development/cannot-redeclare-block-scoped-variable-the-reason-behind-the-error-and-the-way-to-resolve-it/ + if (language === "typescript") { + if (codeSnippet.includes("require(")) { + // except for the attack protection suite , where we need to allow require for compatibility reasons, + // as the SDK URL is dynamic and import might break some builds + if (!codeSnippet.includes('require("https://deviceid.supertokens.io')) { + throw new Error("Do not use 'require' in TS code. Error in " + mdFile); + } + } + codeSnippet = `export { }\n// Original: ${mdFile}\n${codeSnippet}`; // see https://www.aritsltd.com/blog/frontend-development/cannot-redeclare-block-scoped-variable-the-reason-behind-the-error-and-the-way-to-resolve-it/ - // vue requires use of ", ""); - } + // vue requires use of ", ""); + } - let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; - await new Promise(async (res, rej) => { - fs.mkdir('src/plugins/codeTypeChecking/jsEnv/snippets/' + folderName, { recursive: true }, async (err) => { + let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; + await new Promise(async (res, rej) => { + fs.mkdir( + "src/plugins/codeTypeChecking/jsEnv/snippets/" + folderName, + { recursive: true }, + async (err) => { + if (err) { + rej(err); + } else { + await assertThatUserIsNotRemovedDocsVariableByMistake( + "src/plugins/codeTypeChecking/jsEnv/snippets/" + + folderName + + "/index.tsx", + codeSnippet, + ); + fs.writeFile( + "src/plugins/codeTypeChecking/jsEnv/snippets/" + + folderName + + "/index.tsx", + codeSnippet, + function (err) { if (err) { - rej(err); + rej(err); } else { - await assertThatUserIsNotRemovedDocsVariableByMistake('src/plugins/codeTypeChecking/jsEnv/snippets/' + folderName + "/index.tsx", codeSnippet); - fs.writeFile('src/plugins/codeTypeChecking/jsEnv/snippets/' + folderName + "/index.tsx", codeSnippet, function (err) { - if (err) { - rej(err); - } else { - res(); - } - }); + res(); } - }); - }); - } else if (language === "go") { - if (codeSnippet.includes("/supertokens-go/") || codeSnippet.includes("/supertokens-go\n")) { - throw new Error("Do not use supertokens-go package. Use supertokens-golang package. Error in " + mdFile); - } - // we change the last folder path dir to be a valid go module name - let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; - let splittedFolder = folderName.split("/"); - let lastDir = splittedFolder[splittedFolder.length - 1]; - lastDir = lastDir.replaceAll("-", "").replaceAll(".", ""); - splittedFolder[splittedFolder.length - 1] = lastDir; - let newFolderName = splittedFolder.join("/"); - - // adding package on top of go file - codeSnippet = `package ${lastDir}\n/*\n${mdFile}\n*/\n${codeSnippet}`; - - await new Promise(async (res, rej) => { - fs.mkdir('src/plugins/codeTypeChecking/goEnv/snippets/' + newFolderName, { recursive: true }, async (err) => { + }, + ); + } + }, + ); + }); + } else if (language === "go") { + if ( + codeSnippet.includes("/supertokens-go/") || + codeSnippet.includes("/supertokens-go\n") + ) { + throw new Error( + "Do not use supertokens-go package. Use supertokens-golang package. Error in " + + mdFile, + ); + } + // we change the last folder path dir to be a valid go module name + let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; + let splittedFolder = folderName.split("/"); + let lastDir = splittedFolder[splittedFolder.length - 1]; + lastDir = lastDir.replaceAll("-", "").replaceAll(".", ""); + splittedFolder[splittedFolder.length - 1] = lastDir; + let newFolderName = splittedFolder.join("/"); + + // adding package on top of go file + codeSnippet = `package ${lastDir}\n/*\n${mdFile}\n*/\n${codeSnippet}`; + } else if (language === "python") { + codeSnippet = `# ${mdFile}\n${codeSnippet}`; + let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; + await new Promise(async (res, rej) => { + fs.mkdir( + "src/plugins/codeTypeChecking/pythonEnv/snippets/" + folderName, + { recursive: true }, + async (err) => { + if (err) { + rej(err); + } else { + await assertThatUserIsNotRemovedDocsVariableByMistake( + "src/plugins/codeTypeChecking/pythonEnv/snippets/" + + folderName + + "/main.py", + codeSnippet, + ); + fs.writeFile( + "src/plugins/codeTypeChecking/pythonEnv/snippets/" + + folderName + + "/main.py", + codeSnippet, + function (err) { if (err) { - rej(err); + rej(err); } else { - await assertThatUserIsNotRemovedDocsVariableByMistake('src/plugins/codeTypeChecking/goEnv/snippets/' + newFolderName + "/main.go", codeSnippet); - fs.writeFile('src/plugins/codeTypeChecking/goEnv/snippets/' + newFolderName + "/main.go", codeSnippet, function (err) { - if (err) { - rej(err); - } else { - res(); - } - }); + res(); } - }); - }); - } else if (language === "python") { - codeSnippet = `# ${mdFile}\n${codeSnippet}` - let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; - await new Promise(async (res, rej) => { - fs.mkdir('src/plugins/codeTypeChecking/pythonEnv/snippets/' + folderName, { recursive: true }, async (err) => { + }, + ); + } + }, + ); + }); + } else if (language === "php") { + codeSnippet = `// ${mdFile}\n${codeSnippet}`; + let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; + await new Promise(async (res, rej) => { + fs.mkdir( + "src/plugins/codeTypeChecking/phpEnv/snippets/" + folderName, + { recursive: true }, + async (err) => { + if (err) { + rej(err); + } else { + await assertThatUserIsNotRemovedDocsVariableByMistake( + "src/plugins/codeTypeChecking/phpEnv/snippets/" + + folderName + + "/index.php", + codeSnippet, + ); + fs.writeFile( + "src/plugins/codeTypeChecking/phpEnv/snippets/" + + folderName + + "/index.php", + codeSnippet, + function (err) { if (err) { - rej(err); + rej(err); } else { - await assertThatUserIsNotRemovedDocsVariableByMistake('src/plugins/codeTypeChecking/pythonEnv/snippets/' + folderName + "/main.py", codeSnippet); - fs.writeFile('src/plugins/codeTypeChecking/pythonEnv/snippets/' + folderName + "/main.py", codeSnippet, function (err) { - if (err) { - rej(err); - } else { - res(); - } - }); + res(); } - }); - }); - } else if (language === "kotlin") { - let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; - folderName = folderName.replace(".mdx", ""); - let packageNameSplitted = folderName.split("/"); - packageNameSplitted = packageNameSplitted.map(i => { - if (i.includes("-")) { - return "`" + i + "`" - } - return i; - }) - codeSnippet = `package com.example.myapplication${packageNameSplitted.join(".")}\n\n// Original: ${mdFile}\n${codeSnippet}`; - await new Promise(async (res, rej) => { - fs.mkdir('src/plugins/codeTypeChecking/kotlinEnv/app/src/main/java/com/example/myapplication/' + folderName, { recursive: true }, async (err) => { + }, + ); + } + }, + ); + }); + } else if (language === "java") { + codeSnippet = `// ${mdFile}\n${codeSnippet}`; + let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; + await new Promise(async (res, rej) => { + fs.mkdir( + "src/plugins/codeTypeChecking/javaEnv/snippets/" + folderName, + { recursive: true }, + async (err) => { + if (err) { + rej(err); + } else { + await assertThatUserIsNotRemovedDocsVariableByMistake( + "src/plugins/codeTypeChecking/javaEnv/snippets/" + + folderName + + "/sample.java", + codeSnippet, + ); + fs.writeFile( + "src/plugins/codeTypeChecking/javaEnv/snippets/" + + folderName + + "/sample.java", + codeSnippet, + function (err) { if (err) { - rej(err); + rej(err); } else { - await assertThatUserIsNotRemovedDocsVariableByMistake('src/plugins/codeTypeChecking/kotlinEnv/app/src/main/java/com/example/myapplication/' + folderName + "/Code.kt", codeSnippet); - fs.writeFile('src/plugins/codeTypeChecking/kotlinEnv/app/src/main/java/com/example/myapplication/' + folderName + "/Code.kt", codeSnippet, function (err) { - if (err) { - rej(err); - } else { - res(); - } - }); + res(); } - }); - }); - } else if (language === "swift") { - let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; - folderName = folderName.replace(".mdx", ""); - let packageNameSplitted = folderName.split("/"); - packageNameSplitted = packageNameSplitted.map(i => { - if (i.includes("-")) { - return "`" + i + "`" - } - return i; - }) - codeSnippet = `// Original: ${mdFile}\n${codeSnippet}`; - const folderNameSplit = folderName.split("/"); - const lastFolderPath = folderNameSplit[folderNameSplit.length - 1] + new Date().getTime() - await new Promise(async (res, rej) => { - fs.mkdir('src/plugins/codeTypeChecking/iosenv/iosenv/Snippets/' + folderName, { recursive: true }, async (err) => { + }, + ); + } + }, + ); + }); + } else if (language === "csharp") { + codeSnippet = `// ${mdFile}\n${codeSnippet}`; + let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; + await new Promise(async (res, rej) => { + fs.mkdir( + "src/plugins/codeTypeChecking/csharpEnv/snippets/" + folderName, + { recursive: true }, + async (err) => { + if (err) { + rej(err); + } else { + await assertThatUserIsNotRemovedDocsVariableByMistake( + "src/plugins/codeTypeChecking/csharpEnv/snippets/" + + folderName + + "/sample.cs", + codeSnippet, + ); + fs.writeFile( + "src/plugins/codeTypeChecking/csharpEnv/snippets/" + + folderName + + "/sample.cs", + codeSnippet, + function (err) { if (err) { - rej(err); + rej(err); } else { - await assertThatUserIsNotRemovedDocsVariableByMistake('src/plugins/codeTypeChecking/iosenv/iosenv/Snippets/' + folderName + "/Code.swift", codeSnippet); - const filename = 'src/plugins/codeTypeChecking/iosenv/iosenv/Snippets/' + folderName + `/${lastFolderPath}.swift`; - fs.writeFile(filename, codeSnippet, function (err) { - if (err) { - rej(err); - } else { - execSync(`./src/plugins/codeTypeChecking/iosenv/createFile.rb "${filename}"`) - res(); - } - }); + res(); } - }); - }); - } else if (language === "dart") { - let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; - - await new Promise(async (res, rej) => { - fs.mkdir("src/plugins/codeTypeChecking/dart_env/lib/snippets", { recursive: true }, async (err) => { + }, + ); + } + }, + ); + }); + } else if (language === "kotlin") { + let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; + folderName = folderName.replace(".mdx", ""); + let packageNameSplitted = folderName.split("/"); + packageNameSplitted = packageNameSplitted.map((i) => { + if (i.includes("-")) { + return "`" + i + "`"; + } + return i; + }); + codeSnippet = `package com.example.myapplication${packageNameSplitted.join(".")}\n\n// Original: ${mdFile}\n${codeSnippet}`; + await new Promise(async (res, rej) => { + fs.mkdir( + "src/plugins/codeTypeChecking/kotlinEnv/app/src/main/java/com/example/myapplication/" + + folderName, + { recursive: true }, + async (err) => { + if (err) { + rej(err); + } else { + await assertThatUserIsNotRemovedDocsVariableByMistake( + "src/plugins/codeTypeChecking/kotlinEnv/app/src/main/java/com/example/myapplication/" + + folderName + + "/Code.kt", + codeSnippet, + ); + fs.writeFile( + "src/plugins/codeTypeChecking/kotlinEnv/app/src/main/java/com/example/myapplication/" + + folderName + + "/Code.kt", + codeSnippet, + function (err) { if (err) { - rej(err); + rej(err); } else { - res(); + res(); } - }) - }); + }, + ); + } + }, + ); + }); + } else if (language === "swift") { + let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; + folderName = folderName.replace(".mdx", ""); + let packageNameSplitted = folderName.split("/"); + packageNameSplitted = packageNameSplitted.map((i) => { + if (i.includes("-")) { + return "`" + i + "`"; + } + return i; + }); + codeSnippet = `// Original: ${mdFile}\n${codeSnippet}`; + const folderNameSplit = folderName.split("/"); + const lastFolderPath = + folderNameSplit[folderNameSplit.length - 1] + new Date().getTime(); + await new Promise(async (res, rej) => { + fs.mkdir( + "src/plugins/codeTypeChecking/iosenv/iosenv/Snippets/" + folderName, + { recursive: true }, + async (err) => { + if (err) { + rej(err); + } else { + await assertThatUserIsNotRemovedDocsVariableByMistake( + "src/plugins/codeTypeChecking/iosenv/iosenv/Snippets/" + + folderName + + "/Code.swift", + codeSnippet, + ); + const filename = + "src/plugins/codeTypeChecking/iosenv/iosenv/Snippets/" + + folderName + + `/${lastFolderPath}.swift`; + fs.writeFile(filename, codeSnippet, function (err) { + if (err) { + rej(err); + } else { + execSync( + `./src/plugins/codeTypeChecking/iosenv/createFile.rb "${filename}"`, + ); + res(); + } + }); + } + }, + ); + }); + } else if (language === "dart") { + let folderName = mdFile.replaceAll("~", "") + codeBlockCountInFile; - await new Promise(async (res, rej) => { - fs.writeFile("src/plugins/codeTypeChecking/dart_env/lib/snippets/main.dart", mainContent, async (err) => { + await new Promise(async (res, rej) => { + fs.mkdir( + "src/plugins/codeTypeChecking/dart_env/lib/snippets", + { recursive: true }, + async (err) => { + if (err) { + rej(err); + } else { + res(); + } + }, + ); + }); + + await new Promise(async (res, rej) => { + fs.writeFile( + "src/plugins/codeTypeChecking/dart_env/lib/snippets/main.dart", + mainContent, + async (err) => { + if (err) { + rej(err); + } else { + res(); + } + }, + ); + }); + + await new Promise(async (res, rej) => { + fs.mkdir( + "src/plugins/codeTypeChecking/dart_env/lib/snippets/" + folderName, + { recursive: true }, + async (err) => { + if (err) { + rej(err); + } else { + await assertThatUserIsNotRemovedDocsVariableByMistake( + "src/plugins/codeTypeChecking/dart_env/lib/snippets/" + + folderName + + "/index.dart", + codeSnippet, + ); + fs.writeFile( + "src/plugins/codeTypeChecking/dart_env/lib/snippets/" + + folderName + + "/index.dart", + codeSnippet, + function (err) { if (err) { - rej(err); + rej(err); } else { - res(); + res(); } - }) - }); + }, + ); - await new Promise(async (res, rej) => { - fs.mkdir('src/plugins/codeTypeChecking/dart_env/lib/snippets/' + folderName, { recursive: true }, async (err) => { - if (err) { + let dartImportStatement = `import 'package:dart_env/snippets/${folderName}/index.dart';`; + mainContent = dartImportStatement + "\n" + mainContent; + + await new Promise(async (res, rej) => { + fs.writeFile( + "src/plugins/codeTypeChecking/dart_env/lib/snippets/main.dart", + mainContent, + async (err) => { + if (err) { rej(err); - } else { - await assertThatUserIsNotRemovedDocsVariableByMistake('src/plugins/codeTypeChecking/dart_env/lib/snippets/' + folderName + "/index.dart", codeSnippet); - fs.writeFile('src/plugins/codeTypeChecking/dart_env/lib/snippets/' + folderName + "/index.dart", codeSnippet, function (err) { - if (err) { - rej(err); - } else { - res(); - } - }); - - let dartImportStatement = `import 'package:dart_env/snippets/${folderName}/index.dart';`; - mainContent = dartImportStatement + "\n" + mainContent; - - await new Promise(async (res, rej) => { - fs.writeFile("src/plugins/codeTypeChecking/dart_env/lib/snippets/main.dart", mainContent, async (err) => { - if (err) { - rej(err); - } else { - res(); - } - }) - }); - } + } else { + res(); + } + }, + ); }); - }); - } else { - throw new Error("Unsupported language in addCodeSnippetToEnvHelper"); - } + } + }, + ); + }); + } else { + throw new Error("Unsupported language in addCodeSnippetToEnvHelper"); + } } -async function assertThatUserIsNotRemovedDocsVariableByMistake(path, codeSnippet) { - /* first we try and read the contents to see if this has +async function assertThatUserIsNotRemovedDocsVariableByMistake( + path, + codeSnippet, +) { + /* first we try and read the contents to see if this has THIS FILE CONTAINS DOCS VARIABLES. PLEASE DO NOT FORGET TO USE THOSE and if the new code snippet doesn't then, the dev has made a mistake of removing variables...*/ - return new Promise((res, rej) => { - fs.readFile(path, 'utf8', function (err, data) { - if (data !== undefined) { - if (data.includes("THIS FILE CONTAINS DOCS VARIABLES. PLEASE DO NOT FORGET TO USE THOSE") && !codeSnippet.includes("THIS FILE CONTAINS DOCS VARIABLES. PLEASE DO NOT FORGET TO USE THOSE")) { - let message = "DID YOU FORGET TO USE DOCS VARIABLES IN A RECENT CODE CHANGE? PLEASE CHECK" - + "\n\nIf you think this error is unrelated to your changes, try deleting the `snippets` folder for all languages and run again.\n\nThe file path is: " + path - return rej(new Error(message)); - } - } - res(); - }); - }) + return new Promise((res, rej) => { + fs.readFile(path, "utf8", function (err, data) { + if (data !== undefined) { + if ( + data.includes( + "THIS FILE CONTAINS DOCS VARIABLES. PLEASE DO NOT FORGET TO USE THOSE", + ) && + !codeSnippet.includes( + "THIS FILE CONTAINS DOCS VARIABLES. PLEASE DO NOT FORGET TO USE THOSE", + ) + ) { + let message = + "DID YOU FORGET TO USE DOCS VARIABLES IN A RECENT CODE CHANGE? PLEASE CHECK" + + "\n\nIf you think this error is unrelated to your changes, try deleting the `snippets` folder for all languages and run again.\n\nThe file path is: " + + path; + return rej(new Error(message)); + } + } + res(); + }); + }); } function replaceCustomPlaceholdersInLine(child, exportedVariables) { - // A child will either have value properties or more children - - // If it has a value, check if it is using a variable. If it is then replace otherwise skip - if (child.value) { - var valueCopy = child.value; - - // Exclude sections marked by __OMIT_START__ and __OMIT_END__ from the code snippet, along with the tags themselves - valueCopy = valueCopy.replace(/__OMIT_START__[\s\S]*?__OMIT_END__\n?/g, ''); - - let eachLine = valueCopy.split("\n"); - let newLines = []; - for (let i = 0; i < eachLine.length; i++) { - let line = eachLine[i]; - // If the line contains ts-ignore or ends with a comment indicating it should be removed - if (line.includes("@ts-ignore") || /(\/\/|#) typecheck-only, removed from output$/.test(line)) { - continue; - } + // A child will either have value properties or more children - /** - * For snippets that use an older version of supertokens-node we use supertokens-node7 to import - * If the import contains supertokens-node7 we replace it with supertokens-node for the final - * rendered snippet - */ - if (line.includes("supertokens-node7")) { - line = line.split("supertokens-node7").join("supertokens-node"); - newLines.push(line); - continue; - } + // If it has a value, check if it is using a variable. If it is then replace otherwise skip + if (child.value) { + var valueCopy = child.value; - /** - * For snippets that contain supertokensUIInit, we add the dic id param parameter - */ - if (line.includes("supertokensUIInit(")) { - line = line.split("supertokensUIInit(").join("(window as any).supertokensUIInit(\"supertokensui\", "); - newLines.push(line); - continue; - } - if (line.includes("supertokensUI") && !line.includes("supertokens-auth-react-script")) { - line = line.split("supertokensUI").join("(window as any).supertokensUI"); - newLines.push(line); - continue; - } + // Exclude sections marked by __OMIT_START__ and __OMIT_END__ from the code snippet, along with the tags themselves + valueCopy = valueCopy.replace(/__OMIT_START__[\s\S]*?__OMIT_END__\n?/g, ""); - /** - * For snippets that use v5 react-router-dom we use react-router-dom5 to import - * If the import contains react-router-dom5 we replace it with react-router-dom for the final - * rendered snippet - */ - if (line.includes("react-router-dom5")) { - line = line.split("react-router-dom5").join("react-router-dom"); - newLines.push(line); - continue; - } + let eachLine = valueCopy.split("\n"); + let newLines = []; + for (let i = 0; i < eachLine.length; i++) { + let line = eachLine[i]; + // If the line contains ts-ignore or ends with a comment indicating it should be removed + if ( + line.includes("@ts-ignore") || + /(\/\/|#) typecheck-only, removed from output$/.test(line) + ) { + continue; + } - /** - * For python code snippets that contain "# type: ignore", we remove that - * string snippet from the line - */ - if (line.includes("# type: ignore")) { - line = line.split("# type: ignore").join(""); - newLines.push(line); - continue; - } - if (line.includes("#type: ignore")) { - line = line.split("#type: ignore").join(""); - newLines.push(line); - continue; - } - if (line.includes("# type:ignore")) { - line = line.split("# type:ignore").join(""); - newLines.push(line); - continue; - } - if (line.includes("#type:ignore")) { - line = line.split("#type:ignore").join(""); - newLines.push(line); - continue; - } + /** + * For snippets that use an older version of supertokens-node we use supertokens-node7 to import + * If the import contains supertokens-node7 we replace it with supertokens-node for the final + * rendered snippet + */ + if (line.includes("supertokens-node7")) { + line = line.split("supertokens-node7").join("supertokens-node"); + newLines.push(line); + continue; + } - // if the line contains "# pyright: ", then we remove that line - if (line.includes("# pyright:") || line.includes("#pyright:")) { - continue; - } + /** + * For snippets that contain supertokensUIInit, we add the dic id param parameter + */ + if (line.includes("supertokensUIInit(")) { + line = line + .split("supertokensUIInit(") + .join('(window as any).supertokensUIInit("supertokensui", '); + newLines.push(line); + continue; + } + if ( + line.includes("supertokensUI") && + !line.includes("supertokens-auth-react-script") + ) { + line = line + .split("supertokensUI") + .join("(window as any).supertokensUI"); + newLines.push(line); + continue; + } - /** - * For snippets that use supertokens-web-js as an HTML script we import supertokens-web-js-script and supertokens-auth-react-script for types. - * If the line contains this we skip adding the line - */ - if (line.includes("supertokens-web-js-script") || line.includes("supertokens-auth-react-script")) { - continue; - } + /** + * For snippets that use v5 react-router-dom we use react-router-dom5 to import + * If the import contains react-router-dom5 we replace it with react-router-dom for the final + * rendered snippet + */ + if (line.includes("react-router-dom5")) { + line = line.split("react-router-dom5").join("react-router-dom"); + newLines.push(line); + continue; + } - /** - * For iOS snippets we need to use fileprivate class to avoid build errors in Xcode due to duplicate declarations. - * This replaces the fileprivate declaration with a simple class declaration in the final output - */ - if (line.includes("fileprivate class")) { - line = line.split("fileprivate class").join("class"); - newLines.push(line); - continue; - } + /** + * For python code snippets that contain "# type: ignore", we remove that + * string snippet from the line + */ + if (line.includes("# type: ignore")) { + line = line.split("# type: ignore").join(""); + newLines.push(line); + continue; + } + if (line.includes("#type: ignore")) { + line = line.split("#type: ignore").join(""); + newLines.push(line); + continue; + } + if (line.includes("# type:ignore")) { + line = line.split("# type:ignore").join(""); + newLines.push(line); + continue; + } + if (line.includes("#type:ignore")) { + line = line.split("#type:ignore").join(""); + newLines.push(line); + continue; + } - newLines.push(eachLine[i]); - } + // if the line contains "# pyright: ", then we remove that line + if (line.includes("# pyright:") || line.includes("#pyright:")) { + continue; + } - child.value = newLines.join("\n"); - } + /** + * For snippets that use supertokens-web-js as an HTML script we import supertokens-web-js-script and supertokens-auth-react-script for types. + * If the line contains this we skip adding the line + */ + if ( + line.includes("supertokens-web-js-script") || + line.includes("supertokens-auth-react-script") + ) { + continue; + } - // If it has children then repeat recursively - if (child.children) { - child.children = child.children.map(subChild => { - return replaceCustomPlaceholdersInLine(subChild, exportedVariables); - }) + /** + * For iOS snippets we need to use fileprivate class to avoid build errors in Xcode due to duplicate declarations. + * This replaces the fileprivate declaration with a simple class declaration in the final output + */ + if (line.includes("fileprivate class")) { + line = line.split("fileprivate class").join("class"); + newLines.push(line); + continue; + } + + newLines.push(eachLine[i]); } - return child; + child.value = newLines.join("\n"); + } + + // If it has children then repeat recursively + if (child.children) { + child.children = child.children.map((subChild) => { + return replaceCustomPlaceholdersInLine(subChild, exportedVariables); + }); + } + + return child; } -module.exports = { addCodeSnippetToEnv, checkCodeSnippets, replaceCustomPlaceholdersInLine } \ No newline at end of file +module.exports = { + addCodeSnippetToEnv, + checkCodeSnippets, + replaceCustomPlaceholdersInLine, +}; diff --git a/v2/src/plugins/codeTypeChecking/javaEnv/Dockerfile b/v2/src/plugins/codeTypeChecking/javaEnv/Dockerfile new file mode 100644 index 000000000..69953bd17 --- /dev/null +++ b/v2/src/plugins/codeTypeChecking/javaEnv/Dockerfile @@ -0,0 +1,10 @@ +FROM maven:3.8.4-openjdk-11 + +WORKDIR /app + +COPY validate-java.sh . +COPY snippets/ ./snippets + +RUN chmod +x validate-java.sh + +ENTRYPOINT ["./validate-java.sh"] diff --git a/v2/src/plugins/codeTypeChecking/javaEnv/snippets/.gitkeep b/v2/src/plugins/codeTypeChecking/javaEnv/snippets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/v2/src/plugins/codeTypeChecking/javaEnv/snippets/pom.xml b/v2/src/plugins/codeTypeChecking/javaEnv/snippets/pom.xml new file mode 100644 index 000000000..278ab9d24 --- /dev/null +++ b/v2/src/plugins/codeTypeChecking/javaEnv/snippets/pom.xml @@ -0,0 +1,17 @@ + + 4.0.0 + com.example + java-validator + 1.0-SNAPSHOT + + + com.auth0 + java-jwt + 3.18.2 + + + + 11 + 11 + + diff --git a/v2/src/plugins/codeTypeChecking/javaEnv/validate-java.sh b/v2/src/plugins/codeTypeChecking/javaEnv/validate-java.sh new file mode 100644 index 000000000..b60170b2f --- /dev/null +++ b/v2/src/plugins/codeTypeChecking/javaEnv/validate-java.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +cd "snippets" + +mvn compile + +if [ $? -eq 0 ]; then + echo "Java code is valid" + exit 0 +else + echo "Java code is invalid" + exit 1 +fi diff --git a/v2/src/plugins/codeTypeChecking/jsEnv/package.json b/v2/src/plugins/codeTypeChecking/jsEnv/package.json index 2c0ce4097..65a40e418 100644 --- a/v2/src/plugins/codeTypeChecking/jsEnv/package.json +++ b/v2/src/plugins/codeTypeChecking/jsEnv/package.json @@ -43,6 +43,7 @@ "fastify": "^3.27.0", "graphql": "^16.3.0", "hapi": "^18.1.0", + "jose": "^5.9.3", "jsonwebtoken": "^8.5.1", "jwk-to-pem": "^2.0.5", "jwks-rsa": "^3.0.1", @@ -56,14 +57,14 @@ "react-router-dom5": "npm:react-router-dom@^5.3.0", "socket.io": "^4.6.1", "socketio": "^1.0.0", - "supertokens-auth-react": "^0.46.0", - "supertokens-auth-react-script": "github:supertokens/supertokens-auth-react#0.46", - "supertokens-node": "^20.0.0", + "supertokens-auth-react": "github:supertokens/supertokens-auth-react#0.48", + "supertokens-auth-react-script": "github:supertokens/supertokens-auth-react#0.48", + "supertokens-node": "github:supertokens/supertokens-node#feat/add_clientId_secret_and_refreshTokenRotation_settings", "supertokens-node7": "npm:supertokens-node@7.3", "supertokens-react-native": "^5.0.0", - "supertokens-web-js": "^0.13.0", + "supertokens-web-js": "github:supertokens/supertokens-web-js#0.14", "supertokens-web-js-script": "github:supertokens/supertokens-web-js#0.13", - "supertokens-website": "^20.0.1", + "supertokens-website": "github:supertokens/supertokens-website#20.1", "supertokens-website-script": "github:supertokens/supertokens-website#20.0", "typescript": "^4.9.5", "vue": "^3.4.35" diff --git a/v2/src/plugins/codeTypeChecking/phpEnv/Dockerfile b/v2/src/plugins/codeTypeChecking/phpEnv/Dockerfile new file mode 100644 index 000000000..793f6017b --- /dev/null +++ b/v2/src/plugins/codeTypeChecking/phpEnv/Dockerfile @@ -0,0 +1,17 @@ +FROM php:8.2-apache + +RUN apt-get update && apt-get install -y \ + libzip-dev \ + unzip \ + && docker-php-ext-install zip + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +WORKDIR /app + +COPY validate-php.sh . +COPY snippets/ ./snippets + +RUN chmod +x validate-php.sh + +ENTRYPOINT ["./validate-php.sh"] diff --git a/v2/src/plugins/codeTypeChecking/phpEnv/snippets/.gitkeep b/v2/src/plugins/codeTypeChecking/phpEnv/snippets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/v2/src/plugins/codeTypeChecking/phpEnv/snippets/composer.json b/v2/src/plugins/codeTypeChecking/phpEnv/snippets/composer.json new file mode 100644 index 000000000..7b13e53f0 --- /dev/null +++ b/v2/src/plugins/codeTypeChecking/phpEnv/snippets/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "firebase/php-jwt": "^5.0" + } +} diff --git a/v2/src/plugins/codeTypeChecking/phpEnv/validate-php.sh b/v2/src/plugins/codeTypeChecking/phpEnv/validate-php.sh new file mode 100644 index 000000000..9e84a4339 --- /dev/null +++ b/v2/src/plugins/codeTypeChecking/phpEnv/validate-php.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +samples_dir="./snippets" + +cd $samples_dir + +composer install + +validate_php_file() { + file=$1 + echo "Checking syntax for $file..." + php -l $file + if [ $? -ne 0 ]; then + echo "Syntax check failed for $file. The PHP code is invalid." + return 1 + fi + echo "Syntax is valid for $file." + return 0 +} + +find . -name "*.php" | while read file; do + validate_php_file "$file" + if [ $? -ne 0 ]; then + exit 1 + fi +done + +if [ $? -ne 0 ]; then + echo "Validation failed. One or more PHP files contain syntax errors." + exit 1 +fi diff --git a/v2/src/plugins/copyDocsAndCodeTypeChecking.js b/v2/src/plugins/copyDocsAndCodeTypeChecking.js index b71db33e6..30c857038 100644 --- a/v2/src/plugins/copyDocsAndCodeTypeChecking.js +++ b/v2/src/plugins/copyDocsAndCodeTypeChecking.js @@ -1,422 +1,522 @@ -const { execSync } = require('child_process'); -let fs = require('fs'); -let path = require('path'); +const { execSync } = require("child_process"); +let fs = require("fs"); +let path = require("path"); let { addCodeSnippetToEnv, checkCodeSnippets } = require("./codeTypeChecking"); -var exec = require('child_process').exec; +var exec = require("child_process").exec; if (typeof String.prototype.replaceAll === "undefined") { - function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string - } - - String.prototype.replaceAll = function (find, replace) { - var target = this; - return target.replace(new RegExp(escapeRegExp(find), 'g'), replace); - } + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + } + + String.prototype.replaceAll = function (find, replace) { + var target = this; + return target.replace(new RegExp(escapeRegExp(find), "g"), replace); + }; } async function checkFrontendSDKRelatedDocs(mdPath) { - if (mdPath.includes("/change_me")) { - return - } - return new Promise((res, rej) => { - fs.readFile(mdPath, 'utf8', async (err, data) => { - // count the number of times or appears in the data - let count = (data.match(new RegExp('', 'g')) || []).length + (data.match(new RegExp('', 'g')) || []).length; - if (count > 0) { - // make sure that show_ui_switcher: true is also in the file - if (!mdPath.includes("pre-built-ui")) { // we do this cause the pre built UI section in the docs only talks about pre built Ui, so no custom switcher is needed for those pages. - if (data.indexOf("show_ui_switcher: true") === -1) { - rej(new Error("show_ui_switcher: true is not in the file: " + mdPath)); - } - } - - // get count of or appears in the data - let angularCount = (data.match(new RegExp('', 'g')) || []).length + (data.match(new RegExp('', 'g')) || []).length; - if (angularCount !== count) { - rej(new Error("The number of angular and reactjs tabs are not equal: " + mdPath)); - } - } - - // get the lines from the file which contain the string supertokens-auth-react-script - let lines = data.split("\n"); - let linesWithScript = lines.filter(line => line.includes("supertokens-auth-react-script")); - if (linesWithScript.length > 0) { - const ALLOWED_LINES = [ - `import {init as supertokensUIInit} from "supertokens-auth-react-script";`, - `import {init as supertokensUIInit} from "supertokens-auth-react-script"`, - `import supertokensUIMultitenancy from "supertokens-auth-react-script/recipe/multitenancy";`, - `import supertokensUIMultitenancy from "supertokens-auth-react-script/recipe/multitenancy"`, - `import supertokensUISession from "supertokens-auth-react-script/recipe/session";`, - `import supertokensUISession from "supertokens-auth-react-script/recipe/session"`, - `import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword";`, - `import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"`, - `import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty";`, - `import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"`, - `import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless";`, - `import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless"`, - `import supertokensUIEmailVerification from "supertokens-auth-react-script/recipe/emailverification";`, - `import supertokensUIEmailVerification from "supertokens-auth-react-script/recipe/emailverification"`, - `import supertokensUIMultiFactorAuth from "supertokens-auth-react-script/recipe/multifactorauth";`, - `import supertokensUIMultiFactorAuth from "supertokens-auth-react-script/recipe/multifactorauth"`, - `import supertokensUITOTP from "supertokens-auth-react-script/recipe/totp";`, - `import supertokensUITOTP from "supertokens-auth-react-script/recipe/totp"`, - `import supertokensUIUserRoles from "supertokens-auth-react-script/recipe/userroles";`, - `import supertokensUIUserRoles from "supertokens-auth-react-script/recipe/userroles"`, - `import supertokensUI from "supertokens-auth-react-script"`, - `import supertokensUI from "supertokens-auth-react-script";` - ] - - for (const line of linesWithScript) { - if (!ALLOWED_LINES.includes(line.trim())) { - rej(new Error("The line is not allowed: " + line + " in the file: " + mdPath)); - } - } - } - - res(); - }); + if (mdPath.includes("/change_me")) { + return; + } + return new Promise((res, rej) => { + fs.readFile(mdPath, "utf8", async (err, data) => { + // count the number of times or appears in the data + let count = + (data.match(new RegExp('', "g")) || []) + .length + + (data.match(new RegExp("", "g")) || []).length; + if (count > 0) { + // make sure that show_ui_switcher: true is also in the file + if (!mdPath.includes("pre-built-ui")) { + // we do this cause the pre built UI section in the docs only talks about pre built Ui, so no custom switcher is needed for those pages. + if (data.indexOf("show_ui_switcher: true") === -1) { + rej( + new Error("show_ui_switcher: true is not in the file: " + mdPath), + ); + } + } + + // get count of or appears in the data + let angularCount = + (data.match(new RegExp('', "g")) || []) + .length + + (data.match(new RegExp("", "g")) || []) + .length; + if (angularCount !== count) { + rej( + new Error( + "The number of angular and reactjs tabs are not equal: " + mdPath, + ), + ); + } + } + + // get the lines from the file which contain the string supertokens-auth-react-script + let lines = data.split("\n"); + let linesWithScript = lines.filter((line) => + line.includes("supertokens-auth-react-script"), + ); + if (linesWithScript.length > 0) { + const ALLOWED_LINES = [ + `import {init as supertokensUIInit} from "supertokens-auth-react-script";`, + `import {init as supertokensUIInit} from "supertokens-auth-react-script"`, + `import supertokensUIMultitenancy from "supertokens-auth-react-script/recipe/multitenancy";`, + `import supertokensUIMultitenancy from "supertokens-auth-react-script/recipe/multitenancy"`, + `import supertokensUISession from "supertokens-auth-react-script/recipe/session";`, + `import supertokensUISession from "supertokens-auth-react-script/recipe/session"`, + `import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword";`, + `import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"`, + `import supertokensUIOAuth2Provider from "supertokens-auth-react-script/recipe/oauth2provider";`, + `import supertokensUIOAuth2Provider from "supertokens-auth-react-script/recipe/oauth2provider"`, + `import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty";`, + `import supertokensUIThirdParty from "supertokens-auth-react-script/recipe/thirdparty"`, + `import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless";`, + `import supertokensUIPasswordless from "supertokens-auth-react-script/recipe/passwordless"`, + `import supertokensUIEmailVerification from "supertokens-auth-react-script/recipe/emailverification";`, + `import supertokensUIEmailVerification from "supertokens-auth-react-script/recipe/emailverification"`, + `import supertokensUIMultiFactorAuth from "supertokens-auth-react-script/recipe/multifactorauth";`, + `import supertokensUIMultiFactorAuth from "supertokens-auth-react-script/recipe/multifactorauth"`, + `import supertokensUITOTP from "supertokens-auth-react-script/recipe/totp";`, + `import supertokensUITOTP from "supertokens-auth-react-script/recipe/totp"`, + `import supertokensUIUserRoles from "supertokens-auth-react-script/recipe/userroles";`, + `import supertokensUIUserRoles from "supertokens-auth-react-script/recipe/userroles"`, + `import supertokensUI from "supertokens-auth-react-script"`, + `import supertokensUI from "supertokens-auth-react-script";`, + ]; + + for (const line of linesWithScript) { + if (!ALLOWED_LINES.includes(line.trim())) { + rej( + new Error( + "The line is not allowed: " + line + " in the file: " + mdPath, + ), + ); + } + } + } + + res(); }); + }); } module.exports = function (context, opts) { - - return { - name: 'copy-docs-and-code-type-checking', - - async loadContent() { - const skipCopies = process.env.SKIP_COPIES === "true"; - const origDocs = []; - // copy docs.. - await new Promise((res, rej) => { - walk(__dirname + "/../../", async (err, results) => { - if (err) { - return rej(err); - } - results = results.filter(r => r.endsWith(".md") || r.endsWith(".mdx")); - for (const mdPath of results) { - const isCopy = await doCopyDocs(mdPath); - if (!skipCopies || !isCopy) { - origDocs.push(mdPath); - } - } - - // we check some extra conditions related to https://github.com/supertokens/docs/pull/822/ PR here - // which are not related to copy docs or code type checking, but this place is fine for it anyway: - for (const mdPath of results) { - await checkFrontendSDKRelatedDocs(mdPath); - } - res(); - }); - }) - - let check = process.env.CODE_TYPE_CHECK; - if (check === undefined && process.env.MODE === "production") { - check = "all"; + return { + name: "copy-docs-and-code-type-checking", + + async loadContent() { + const skipCopies = process.env.SKIP_COPIES === "true"; + const origDocs = []; + // copy docs.. + await new Promise((res, rej) => { + walk(__dirname + "/../../", async (err, results) => { + if (err) { + return rej(err); + } + results = results.filter( + (r) => r.endsWith(".md") || r.endsWith(".mdx"), + ); + for (const mdPath of results) { + const isCopy = await doCopyDocs(mdPath); + if (!skipCopies || !isCopy) { + origDocs.push(mdPath); } - - if (check !== undefined && check !== "nothing") { - let splittedCheck = check.split(","); - const isSwiftEnabled = splittedCheck.filter(i => i === "swift").length >= 1; - - if (isSwiftEnabled) { - // Reset iosenv - execSync("./src/plugins/codeTypeChecking/iosenv/resetenv.rb") - - if (!fs.existsSync("src/plugins/codeTypeChecking/iosenv/iosenv/Snippets")) { - fs.mkdirSync("src/plugins/codeTypeChecking/iosenv/iosenv/Snippets"); - } - - fs.readdirSync("src/plugins/codeTypeChecking/iosenv/iosenv/Snippets").forEach(file => { - fs.rmSync("src/plugins/codeTypeChecking/iosenv/iosenv/Snippets/" + file, { - recursive: true, - force: true, - }); - }); - } - - // add code snippets to their respective env.. - for (const mdPath of origDocs) { - await addCodeSnippetToEnv(mdPath, isSwiftEnabled); - } - } - - // now we compile code snippets to make sure their types are correct.. - try { - // modify this block to add a new language - if (check !== undefined && check !== "nothing") { - let splittedCheck = check.split(","); - if (splittedCheck.filter(i => i === "all").length >= 1 || - splittedCheck.filter(i => i === "typescript").length >= 1) { - await checkCodeSnippets("typescript"); - } - if (splittedCheck.filter(i => i === "all").length >= 1 || - splittedCheck.filter(i => i === "go").length >= 1) { - await checkCodeSnippets("go"); - } - if (splittedCheck.filter(i => i === "all").length >= 1 || - splittedCheck.filter(i => i === "python").length >= 1) { - await checkCodeSnippets("python"); - } - if (splittedCheck.filter(i => i === "all").length >= 1 || - splittedCheck.filter(i => i === "kotlin").length >= 1) { - await checkCodeSnippets("kotlin"); - } - if (splittedCheck.filter(i => i === "all").length >= 1 || - splittedCheck.filter(i => i === "dart").length >= 1) { - await checkCodeSnippets("dart"); - } - - // We do not do this in the case of "all" intentionally because - // iOS code checking needs MacOS - if (splittedCheck.filter(i => i === "swift").length >= 1) { - await checkCodeSnippets("swift"); - } - } - } catch (err) { - if (process.env.MODE === "production") { - throw err; - } - } - }, - }; + } + + // we check some extra conditions related to https://github.com/supertokens/docs/pull/822/ PR here + // which are not related to copy docs or code type checking, but this place is fine for it anyway: + for (const mdPath of results) { + await checkFrontendSDKRelatedDocs(mdPath); + } + res(); + }); + }); + + let check = process.env.CODE_TYPE_CHECK; + if (check === undefined && process.env.MODE === "production") { + check = "all"; + } + + if (check !== undefined && check !== "nothing") { + let splittedCheck = check.split(","); + const isSwiftEnabled = + splittedCheck.filter((i) => i === "swift").length >= 1; + + if (isSwiftEnabled) { + // Reset iosenv + execSync("./src/plugins/codeTypeChecking/iosenv/resetenv.rb"); + + if ( + !fs.existsSync( + "src/plugins/codeTypeChecking/iosenv/iosenv/Snippets", + ) + ) { + fs.mkdirSync("src/plugins/codeTypeChecking/iosenv/iosenv/Snippets"); + } + + fs.readdirSync( + "src/plugins/codeTypeChecking/iosenv/iosenv/Snippets", + ).forEach((file) => { + fs.rmSync( + "src/plugins/codeTypeChecking/iosenv/iosenv/Snippets/" + file, + { + recursive: true, + force: true, + }, + ); + }); + } + + // add code snippets to their respective env.. + for (const mdPath of origDocs) { + await addCodeSnippetToEnv(mdPath, isSwiftEnabled); + } + } + + // now we compile code snippets to make sure their types are correct.. + try { + // modify this block to add a new language + if (check !== undefined && check !== "nothing") { + let splittedCheck = check.split(","); + if ( + splittedCheck.filter((i) => i === "all").length >= 1 || + splittedCheck.filter((i) => i === "typescript").length >= 1 + ) { + await checkCodeSnippets("typescript"); + } + if ( + splittedCheck.filter((i) => i === "all").length >= 1 || + splittedCheck.filter((i) => i === "go").length >= 1 + ) { + await checkCodeSnippets("go"); + } + if ( + splittedCheck.filter((i) => i === "all").length >= 1 || + splittedCheck.filter((i) => i === "python").length >= 1 + ) { + await checkCodeSnippets("python"); + } + if ( + splittedCheck.filter((i) => i === "all").length >= 1 || + splittedCheck.filter((i) => i === "kotlin").length >= 1 + ) { + await checkCodeSnippets("kotlin"); + } + if ( + splittedCheck.filter((i) => i === "all").length >= 1 || + splittedCheck.filter((i) => i === "dart").length >= 1 + ) { + await checkCodeSnippets("dart"); + } + if ( + splittedCheck.filter((i) => i === "all").length >= 1 || + splittedCheck.filter((i) => i === "java").length >= 1 + ) { + await checkCodeSnippets("java"); + } + if ( + splittedCheck.filter((i) => i === "all").length >= 1 || + splittedCheck.filter((i) => i === "csharp").length >= 1 + ) { + await checkCodeSnippets("csharp"); + } + // if ( + // splittedCheck.filter((i) => i === "all").length >= 1 || + // splittedCheck.filter((i) => i === "php").length >= 1 + // ) { + // await checkCodeSnippets("php"); + // } + + // We do not do this in the case of "all" intentionally because + // iOS code checking needs MacOS + if (splittedCheck.filter((i) => i === "swift").length >= 1) { + await checkCodeSnippets("swift"); + } + } + } catch (err) { + if (process.env.MODE === "production") { + throw err; + } + } + }, + }; }; - async function doCopyDocs(mdFile, ignore) { - return new Promise((res, rej) => { - fs.readFile(mdFile, 'utf8', async (err, data) => { - if (err) { - return rej(err); + return new Promise((res, rej) => { + fs.readFile(mdFile, "utf8", async (err, data) => { + if (err) { + return rej(err); + } + + let originalData = data; + + let lines = originalData.split("\n"); + + let pathLine = undefined; + let copyType = undefined; + let containsBoth = false; + let copyPathsAndIDs = []; + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + if ( + line.trim() === "" || + line.trim() === "" + ) { + if (line.trim() === "" && ignore === "SECTION") { + continue; + } + if (line.trim() === "" && ignore === "DOCS") { + continue; + } + if (line.trim() === "") { + if (copyType === "DOCS") { + containsBoth = true; + } else { + copyType = "SECTION"; + copyPathsAndIDs.push({ + path: lines[i + 1], + id: lines[i + 2], + }); } - - let originalData = data; - - let lines = originalData.split("\n"); - - let pathLine = undefined; - let copyType = undefined; - let containsBoth = false; - let copyPathsAndIDs = []; - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; - if (line.trim() === "" || line.trim() === "") { - if (line.trim() === "" && ignore === "SECTION") { - continue; - } - if (line.trim() === "" && ignore === "DOCS") { - continue; - } - if (line.trim() === "") { - if (copyType === "DOCS") { - containsBoth = true; - } else { - copyType = "SECTION" - copyPathsAndIDs.push({ - path: lines[i + 1], - id: lines[i + 2] - }) - } - } else { - if (copyType === "SECTION") { - containsBoth = true; - pathLine = lines[i + 1] - } else { - pathLine = lines[i + 1] - copyType = "DOCS" - } - } - } + } else { + if (copyType === "SECTION") { + containsBoth = true; + pathLine = lines[i + 1]; + } else { + pathLine = lines[i + 1]; + copyType = "DOCS"; } - - if (copyType === undefined) { - return res(false); + } + } + } + + if (copyType === undefined) { + return res(false); + } + + if (containsBoth && ignore === undefined) { + let originalDocsPath = path.resolve( + __dirname + + "/../../" + + pathLine.replace(/ /g, "").replace("", ""), + ); + // first we want to resolve all the sections of the originalDocs + await doCopyDocs(originalDocsPath, "DOCS"); // ignore copy docs.. + + // then we want to do copy docs on this file + let resp = await doCopyDocs(mdFile, "SECTION"); // ignore copy section. + return res(resp); + } + + if (copyType === "DOCS") { + pathLine = path.resolve( + __dirname + + "/../../" + + pathLine.replace(/ /g, "").replace("", ""), + ); + + if (pathLine === mdFile) { + return res(false); + } + + let sourceData = await getDataToCopyFromFile(pathLine, lines); + + let destinationData = await getDataToCopyFromFile(mdFile, lines); + + if (sourceData === destinationData) { + return res(true); + } else { + fs.writeFile(mdFile, sourceData, "utf8", function (err) { + if (err) { + return rej(err); } - - if (containsBoth && ignore === undefined) { - let originalDocsPath = path.resolve(__dirname + "/../../" + pathLine.replace(/ /g, '').replace("", "")); - // first we want to resolve all the sections of the originalDocs - await doCopyDocs(originalDocsPath, "DOCS"); // ignore copy docs.. - - // then we want to do copy docs on this file - let resp = await doCopyDocs(mdFile, "SECTION"); // ignore copy section. - return res(resp); + res(true); + }); + } + } else { + let sourceData = await getDataToCopySectionFromFile( + lines, + copyPathsAndIDs, + ); + + let destinationData = await new Promise((res, rej) => { + fs.readFile(mdFile, "utf8", function (err, toCopyData) { + if (err) { + return rej(err); } + res(toCopyData); + }); + }); - if (copyType === "DOCS") { - - pathLine = path.resolve(__dirname + "/../../" + pathLine.replace(/ /g, '').replace("", "")); - - if (pathLine === mdFile) { - return res(false); - } - - let sourceData = await getDataToCopyFromFile(pathLine, lines); - - let destinationData = await getDataToCopyFromFile(mdFile, lines); - - if (sourceData === destinationData) { - return res(true); - } else { - fs.writeFile(mdFile, sourceData, 'utf8', function (err) { - if (err) { - return rej(err); - } - res(true); - }); - } - } else { - let sourceData = await getDataToCopySectionFromFile(lines, copyPathsAndIDs); - - let destinationData = await new Promise((res, rej) => { - fs.readFile(mdFile, 'utf8', function (err, toCopyData) { - if (err) { - return rej(err) - } - res(toCopyData) - }) - }); - - if (sourceData === destinationData) { - return res(false); - } else { - fs.writeFile(mdFile, sourceData, 'utf8', function (err) { - if (err) { - return rej(err); - } - res(false); - }); - } + if (sourceData === destinationData) { + return res(false); + } else { + fs.writeFile(mdFile, sourceData, "utf8", function (err) { + if (err) { + return rej(err); } - }); + res(false); + }); + } + } }); + }); } async function getDataToCopySectionFromFile(lines, copyPathsAndIDs) { - let finalData = [...lines]; - for (let k = 0; k < copyPathsAndIDs.length; k++) { - await new Promise((res, rej) => { - let ogPathLine = copyPathsAndIDs[k].path - let ogId = copyPathsAndIDs[k].id - let pathLine = ogPathLine - pathLine = path.resolve(__dirname + "/../../" + pathLine.replace(/ /g, '').replace("", "")); - fs.readFile(pathLine, 'utf8', async function (err, toCopyData) { - if (err) { - return rej(err); - } - - toCopyData = toCopyData.trim(); - - finalDataIndex = 0; - - for (; finalDataIndex < finalData.length; finalDataIndex++) { - if (finalData[finalDataIndex].trim() === "") { - if (finalData[finalDataIndex + 1].trim() === ogPathLine && finalData[finalDataIndex + 2].trim() === ogId) { - finalDataIndex += 3; - break; - } else { - // do nothing.. - } - } - } - - let toRemoveIndex = finalDataIndex; - for (let i = finalDataIndex; i < finalData.length; i++) { - if (finalData[i] === "") { - toRemoveIndex = i; - break; - } - } - finalData = [...finalData.slice(0, finalDataIndex), ...finalData.slice(toRemoveIndex)] - - let copyLines = toCopyData.split("\n"); - let startCopying = false; - for (let i = 0; i < copyLines.length; i++) { - if (startCopying && copyLines[i].trim() === "") { - break; - } - if (copyLines[i].trim() === "" && - copyLines[i + 1].trim() === ogPathLine && - copyLines[i + 2].trim() === ogId) { - startCopying = true; - i++; - i++; - continue; - } - if (!startCopying) { - continue; - } - finalData = [...finalData.slice(0, finalDataIndex), copyLines[i], ...finalData.slice(finalDataIndex)] - finalDataIndex++ - } - res(); - }); - }); - } - return finalData.join("\n") + let finalData = [...lines]; + for (let k = 0; k < copyPathsAndIDs.length; k++) { + await new Promise((res, rej) => { + let ogPathLine = copyPathsAndIDs[k].path; + let ogId = copyPathsAndIDs[k].id; + let pathLine = ogPathLine; + pathLine = path.resolve( + __dirname + + "/../../" + + pathLine.replace(/ /g, "").replace("", ""), + ); + fs.readFile(pathLine, "utf8", async function (err, toCopyData) { + if (err) { + return rej(err); + } + + toCopyData = toCopyData.trim(); + + finalDataIndex = 0; + + for (; finalDataIndex < finalData.length; finalDataIndex++) { + if (finalData[finalDataIndex].trim() === "") { + if ( + finalData[finalDataIndex + 1].trim() === ogPathLine && + finalData[finalDataIndex + 2].trim() === ogId + ) { + finalDataIndex += 3; + break; + } else { + // do nothing.. + } + } + } + + let toRemoveIndex = finalDataIndex; + for (let i = finalDataIndex; i < finalData.length; i++) { + if (finalData[i] === "") { + toRemoveIndex = i; + break; + } + } + finalData = [ + ...finalData.slice(0, finalDataIndex), + ...finalData.slice(toRemoveIndex), + ]; + + let copyLines = toCopyData.split("\n"); + let startCopying = false; + for (let i = 0; i < copyLines.length; i++) { + if ( + startCopying && + copyLines[i].trim() === "" + ) { + break; + } + if ( + copyLines[i].trim() === "" && + copyLines[i + 1].trim() === ogPathLine && + copyLines[i + 2].trim() === ogId + ) { + startCopying = true; + i++; + i++; + continue; + } + if (!startCopying) { + continue; + } + finalData = [ + ...finalData.slice(0, finalDataIndex), + copyLines[i], + ...finalData.slice(finalDataIndex), + ]; + finalDataIndex++; + } + res(); + }); + }); + } + return finalData.join("\n"); } async function getDataToCopyFromFile(pathLine, lines) { - return new Promise((res, rej) => { - fs.readFile(pathLine, 'utf8', function (err, toCopyData) { - if (err) { - return rej(err); - } - - toCopyData = toCopyData.trim(); - - let finalData = ""; - - for (let i = 0; i < lines.length; i++) { - finalData += lines[i] + "\n"; - if (lines[i].trim() === "") { - finalData += lines[i + 1] + "\n"; - break; - } - } - - let copyLines = toCopyData.split("\n"); - let startCopying = false; - for (let i = 0; i < copyLines.length; i++) { - if (copyLines[i].trim() === "") { - startCopying = true; - i++; - continue; - } - if (!startCopying) { - continue; - } - finalData += copyLines[i] + "\n"; - } - res(finalData); - }); + return new Promise((res, rej) => { + fs.readFile(pathLine, "utf8", function (err, toCopyData) { + if (err) { + return rej(err); + } + + toCopyData = toCopyData.trim(); + + let finalData = ""; + + for (let i = 0; i < lines.length; i++) { + finalData += lines[i] + "\n"; + if (lines[i].trim() === "") { + finalData += lines[i + 1] + "\n"; + break; + } + } + + let copyLines = toCopyData.split("\n"); + let startCopying = false; + for (let i = 0; i < copyLines.length; i++) { + if (copyLines[i].trim() === "") { + startCopying = true; + i++; + continue; + } + if (!startCopying) { + continue; + } + finalData += copyLines[i] + "\n"; + } + res(finalData); }); + }); } let walk = function (dir, done) { - var results = []; - fs.readdir(dir, function (err, list) { - if (err) return done(err); - var i = 0; - (function next() { - var file = list[i++]; - if (!file) return done(null, results); - file = path.resolve(dir, file); - fs.stat(file, function (err, stat) { - let name = file.toString(); - if (stat && stat.isDirectory() && !name.endsWith("node_modules") && - !name.endsWith(".docusaurus") && !name.endsWith("build")) { - walk(file, function (err, res) { - results = results.concat(res); - next(); - }); - } else { - if (!name.endsWith("/v2/HOW_TO_NEW_DOCS.md") && !name.endsWith("/v2/README.md")) { - results.push(file); - } - next(); - } - }); - })(); - }); -}; \ No newline at end of file + var results = []; + fs.readdir(dir, function (err, list) { + if (err) return done(err); + var i = 0; + (function next() { + var file = list[i++]; + if (!file) return done(null, results); + file = path.resolve(dir, file); + fs.stat(file, function (err, stat) { + let name = file.toString(); + if ( + stat && + stat.isDirectory() && + !name.endsWith("node_modules") && + !name.endsWith(".docusaurus") && + !name.endsWith("build") + ) { + walk(file, function (err, res) { + results = results.concat(res); + next(); + }); + } else { + if ( + !name.endsWith("/v2/HOW_TO_NEW_DOCS.md") && + !name.endsWith("/v2/README.md") + ) { + results.push(file); + } + next(); + } + }); + })(); + }); +}; diff --git a/v2/src/theme/NavbarItem/index.js b/v2/src/theme/NavbarItem/index.js index fc1093ef2..baa4e5463 100644 --- a/v2/src/theme/NavbarItem/index.js +++ b/v2/src/theme/NavbarItem/index.js @@ -4,14 +4,12 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -import React from 'react'; -import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem'; -import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem'; -import SearchNavbarItem from '@theme/NavbarItem/SearchNavbarItem'; -import { useLocation } from '@docusaurus/router'; -import { - useVersions, -} from '@theme/hooks/useDocs'; +import React from "react"; +import DefaultNavbarItem from "@theme/NavbarItem/DefaultNavbarItem"; +import LocaleDropdownNavbarItem from "@theme/NavbarItem/LocaleDropdownNavbarItem"; +import SearchNavbarItem from "@theme/NavbarItem/SearchNavbarItem"; +import { useLocation } from "@docusaurus/router"; +import { useVersions } from "@theme/hooks/useDocs"; const NavbarItemComponents = { default: () => DefaultNavbarItem, @@ -21,18 +19,18 @@ const NavbarItemComponents = { // See https://github.com/facebook/docusaurus/issues/3360 docsVersion: () => // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require - require('@theme/NavbarItem/DocsVersionNavbarItem').default, + require("@theme/NavbarItem/DocsVersionNavbarItem").default, docsVersionDropdown: () => // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require - require('@theme/NavbarItem/DocsVersionDropdownNavbarItem').default, + require("@theme/NavbarItem/DocsVersionDropdownNavbarItem").default, doc: () => // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require - require('@theme/NavbarItem/DocNavbarItem').default, + require("@theme/NavbarItem/DocNavbarItem").default, recipeDropDown: () => require("./recipeSelector").default, }; -const getNavbarItemComponent = (type = 'default') => { +const getNavbarItemComponent = (type = "default") => { const navbarItemComponent = NavbarItemComponents[type]; if (!navbarItemComponent) { @@ -43,28 +41,28 @@ const getNavbarItemComponent = (type = 'default') => { }; export default function NavbarItem({ type, ...props }) { - let newProps = { ...props }; - const { docsPluginId } = props - const ourDocsPluginId = docsPluginId === "default" ? "community" : docsPluginId; - const { pathname } = useLocation() + const { docsPluginId } = props; + const ourDocsPluginId = + docsPluginId === "default" ? "community" : docsPluginId; + const { pathname } = useLocation(); const currDocs = pathname.split("/")[2]; // show only version if applies to current docs. - if (type === 'docsVersionDropdown') { + if (type === "docsVersionDropdown") { if (ourDocsPluginId !== currDocs) { return null; } // we show the version only if there is more than one version to show. - const versions = useVersions(docsPluginId) + const versions = useVersions(docsPluginId); if (versions.length === 1) { return null; } - if (LINK_TO_OLDER_VERSIONS.filter(i => i === currDocs).length === 1) { + if (LINK_TO_OLDER_VERSIONS.filter((i) => i === currDocs).length === 1) { newProps = { ...newProps, dropdownItemsAfter: [ @@ -73,18 +71,17 @@ export default function NavbarItem({ type, ...props }) { isNavLink: true, label: "Older versions", to: `https://supertokens.com/docs/${currDocs}/versions`, - isActive: () => { }, - onClick: () => { } - } - ] - } + isActive: () => {}, + onClick: () => {}, + }, + ], + }; } } - // change github href link based on current docs being viewed. if (props.label !== undefined && props.label === "GitHub") { - if (DO_NOT_SHOW_GITHUB_BUTTON.filter(i => i === currDocs).length > 0) { + if (DO_NOT_SHOW_GITHUB_BUTTON.filter((i) => i === currDocs).length > 0) { return null; } const shouldUseCore = currDocs === "core" || currDocs === "guides"; @@ -94,19 +91,33 @@ export default function NavbarItem({ type, ...props }) { toReplace = "node"; } if (toReplace === "community") { - toReplace = "core" + toReplace = "core"; } newProps = { ...props, href: props.href.replace("to_replace", toReplace), - } + }; } const NavbarItemComponent = getNavbarItemComponent(type); return ; } -const DO_NOT_SHOW_GITHUB_BUTTON = ["emailpassword", "thirdparty", "thirdpartyemailpassword", "thirdpartypasswordless", "passwordless", "session", "contribute", "phonepassword", "userroles", "mfa", "microservice_auth", "multitenancy"]; +const DO_NOT_SHOW_GITHUB_BUTTON = [ + "emailpassword", + "thirdparty", + "thirdpartyemailpassword", + "thirdpartypasswordless", + "passwordless", + "session", + "contribute", + "phonepassword", + "userroles", + "mfa", + "private-access-token-authentication", + "multitenancy", +]; + +const LINK_TO_OLDER_VERSIONS = ["nodejs", "auth-react", "website"]; -const LINK_TO_OLDER_VERSIONS = ["nodejs", "auth-react", "website"]; \ No newline at end of file diff --git a/v2/src/theme/NavbarItem/recipeSelector.js b/v2/src/theme/NavbarItem/recipeSelector.js index d95edb36c..277a6b3c2 100644 --- a/v2/src/theme/NavbarItem/recipeSelector.js +++ b/v2/src/theme/NavbarItem/recipeSelector.js @@ -33,11 +33,17 @@ export default function RecipeSelector(props) { case "mfa": return "Multi factor auth"; case "microservice_auth": - return "Microservice Auth"; + return "Microservice Authentication"; case "userdashboard": return "User Management Dashboard"; case "multitenancy": return "Multi Tenancy"; + case "anomaly_detection": + return "Attack Protection Suite"; + case "attackprotectionsuite": + return "Attack Protection Suite"; + case "unified-login": + return "Unified Login"; default: return "Select Recipe"; } @@ -192,6 +198,13 @@ export default function RecipeSelector(props) { Attack Protection Suite +
  • + Unified Login +
  • diff --git a/v2/static/img/guides/oauth-logo-orange.svg b/v2/static/img/guides/oauth-logo-orange.svg new file mode 100644 index 000000000..ed65036b0 --- /dev/null +++ b/v2/static/img/guides/oauth-logo-orange.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v2/static/img/guides/oauth-logo.svg b/v2/static/img/guides/oauth-logo.svg new file mode 100644 index 000000000..00d25690e --- /dev/null +++ b/v2/static/img/guides/oauth-logo.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v2/static/img/guides/unified-login-orange.svg b/v2/static/img/guides/unified-login-orange.svg new file mode 100644 index 000000000..df21c1a03 --- /dev/null +++ b/v2/static/img/guides/unified-login-orange.svg @@ -0,0 +1,3 @@ + + + diff --git a/v2/static/img/guides/unified-login.svg b/v2/static/img/guides/unified-login.svg new file mode 100644 index 000000000..5d3fa29f7 --- /dev/null +++ b/v2/static/img/guides/unified-login.svg @@ -0,0 +1,3 @@ + + + diff --git a/v2/static/img/oauth/authorization-code-flow.png b/v2/static/img/oauth/authorization-code-flow.png new file mode 100644 index 000000000..3845155db Binary files /dev/null and b/v2/static/img/oauth/authorization-code-flow.png differ diff --git a/v2/static/img/oauth/client-credentials-flow.png b/v2/static/img/oauth/client-credentials-flow.png new file mode 100644 index 000000000..891d4ff87 Binary files /dev/null and b/v2/static/img/oauth/client-credentials-flow.png differ diff --git a/v2/static/img/oauth/machine-to-machine.png b/v2/static/img/oauth/machine-to-machine.png new file mode 100644 index 000000000..70c718a96 Binary files /dev/null and b/v2/static/img/oauth/machine-to-machine.png differ diff --git a/v2/static/img/oauth/multiple-frontend-domains-with-a-single-backend.png b/v2/static/img/oauth/multiple-frontend-domains-with-a-single-backend.png new file mode 100644 index 000000000..3810090d9 Binary files /dev/null and b/v2/static/img/oauth/multiple-frontend-domains-with-a-single-backend.png differ diff --git a/v2/static/img/oauth/multiple-frontend-domains-with-separate-backends.png b/v2/static/img/oauth/multiple-frontend-domains-with-separate-backends.png new file mode 100644 index 000000000..3f607adaf Binary files /dev/null and b/v2/static/img/oauth/multiple-frontend-domains-with-separate-backends.png differ diff --git a/v2/static/img/oauth/reuse-web-ui-for-mobile.png b/v2/static/img/oauth/reuse-web-ui-for-mobile.png new file mode 100644 index 000000000..5a98fc2c3 Binary files /dev/null and b/v2/static/img/oauth/reuse-web-ui-for-mobile.png differ diff --git a/v2/thirdparty/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/thirdparty/advanced-customizations/apis-override/custom-response/throwing-error.mdx index fd453819d..d154a65b7 100644 --- a/v2/thirdparty/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/thirdparty/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -347,7 +347,7 @@ import { backendConfig } from "@/app/config/backend"; SuperTokens.init(backendConfig()); // in the app/api/auth/[...path]/route.ts file -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); const withCustomErrorHandling = async (request: NextRequest) => { try { diff --git a/v2/thirdparty/common-customizations/account-linking/manual-account-linking.mdx b/v2/thirdparty/common-customizations/account-linking/manual-account-linking.mdx index 1efedbddc..14e06af8f 100644 --- a/v2/thirdparty/common-customizations/account-linking/manual-account-linking.mdx +++ b/v2/thirdparty/common-customizations/account-linking/manual-account-linking.mdx @@ -300,9 +300,11 @@ def link_accounts_helper(primary_user_id: str, recipe_user_id: RecipeUserId):
    -
    +
    + + ## Unlinking accounts If you want to unlink an account from its primary user ID, you can use the following function: diff --git a/v2/thirdparty/common-customizations/email-verification/about.mdx b/v2/thirdparty/common-customizations/email-verification/about.mdx index 63b5e5ff1..ceb8fd555 100644 --- a/v2/thirdparty/common-customizations/email-verification/about.mdx +++ b/v2/thirdparty/common-customizations/email-verification/about.mdx @@ -17,6 +17,7 @@ import TabItem from "@theme/TabItem"; import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import AngularUIImplementation from "/src/components/reusableSnippets/angularUIImplementation" import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Enable email verification @@ -25,6 +26,8 @@ import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplement Email verification is turned off by default. It is strongly encouraged to enable it to ensure the authenticity of your users. ::: + + diff --git a/v2/thirdparty/common-customizations/email-verification/protecting-routes.mdx b/v2/thirdparty/common-customizations/email-verification/protecting-routes.mdx index b8759e741..6e13b1a87 100644 --- a/v2/thirdparty/common-customizations/email-verification/protecting-routes.mdx +++ b/v2/thirdparty/common-customizations/email-verification/protecting-routes.mdx @@ -24,11 +24,14 @@ import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import AngularUIImplementation from "/src/components/reusableSnippets/angularUIImplementation" import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" import {Answer} from "/src/components/question" +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting backend APIs and website routes ## Protecting backend API routes + + ### Add email verification checks to all API routes If you want to protect all your backend API routes with email verification checks, set the `mode` to `REQUIRED` in the `EmailVerification` config. Routes protected with the `verifySession` middleware will now additionally check for email verification status. diff --git a/v2/thirdparty/common-customizations/sessions/claims/access-token-payload.mdx b/v2/thirdparty/common-customizations/sessions/claims/access-token-payload.mdx index 3563fef3a..f0290e6cf 100644 --- a/v2/thirdparty/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/thirdparty/common-customizations/sessions/claims/access-token-payload.mdx @@ -22,6 +22,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 1. Using the access token payload +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## Add custom claims to the access token payload :::important diff --git a/v2/thirdparty/common-customizations/sessions/claims/claim-validators.mdx b/v2/thirdparty/common-customizations/sessions/claims/claim-validators.mdx index fdc63bbb1..21f55d69d 100644 --- a/v2/thirdparty/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/thirdparty/common-customizations/sessions/claims/claim-validators.mdx @@ -23,6 +23,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 2. Using claim validators +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## What are session claims? SuperTokens session has a property called `accessTokenPayload`. This is a `JSON` object that's stored in a user's session which can be accessed on the frontend and backend. The key-values in this JSON payload are called claims. diff --git a/v2/thirdparty/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/thirdparty/common-customizations/sessions/protecting-frontend-routes.mdx index 232bba79f..9e5719247 100644 --- a/v2/thirdparty/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/thirdparty/common-customizations/sessions/protecting-frontend-routes.mdx @@ -14,9 +14,12 @@ import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthFrontendVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting frontend routes + + diff --git a/v2/thirdparty/common-customizations/sessions/session-verification-in-api/get-session.mdx b/v2/thirdparty/common-customizations/sessions/session-verification-in-api/get-session.mdx index 94a21fa6d..ce8decce4 100644 --- a/v2/thirdparty/common-customizations/sessions/session-verification-in-api/get-session.mdx +++ b/v2/thirdparty/common-customizations/sessions/session-verification-in-api/get-session.mdx @@ -13,10 +13,13 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session Verification using `getSession` + + If you want to use a non-middleware form of `verifySession`, you can use the `getSession` function. ## Using `getSession` diff --git a/v2/thirdparty/common-customizations/sessions/session-verification-in-api/verify-session.mdx b/v2/thirdparty/common-customizations/sessions/session-verification-in-api/verify-session.mdx index f176eab66..e7a1df653 100644 --- a/v2/thirdparty/common-customizations/sessions/session-verification-in-api/verify-session.mdx +++ b/v2/thirdparty/common-customizations/sessions/session-verification-in-api/verify-session.mdx @@ -10,10 +10,13 @@ import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" ## Verifying a session using the `verifySession` middleware + + For your APIs that require a user to be logged in, use the `verifySession` middleware: diff --git a/v2/thirdparty/common-customizations/sessions/ssr.mdx b/v2/thirdparty/common-customizations/sessions/ssr.mdx index 863e5cb5b..e89210f16 100644 --- a/v2/thirdparty/common-customizations/sessions/ssr.mdx +++ b/v2/thirdparty/common-customizations/sessions/ssr.mdx @@ -17,9 +17,12 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session verification during server side rendering + + :::important Getting access to the session during server side rendering is only possible using cookie-based sessions. This is the default setting, but you have to keep this in mind if you want to switch to header-based sessions. ::: diff --git a/v2/thirdparty/common-customizations/sessions/with-jwt/jwt-verification.mdx b/v2/thirdparty/common-customizations/sessions/with-jwt/jwt-verification.mdx index d1d014c9c..c39494207 100644 --- a/v2/thirdparty/common-customizations/sessions/with-jwt/jwt-verification.mdx +++ b/v2/thirdparty/common-customizations/sessions/with-jwt/jwt-verification.mdx @@ -15,14 +15,18 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import AppInfoForm from "/src/components/appInfoForm"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Manually verify the JWT + + There are three steps in doing session verification using JWTs: - Verify the JWT signature and expiry using a JWT verification library - Check for custom claim values for authorization. - Preventing CSRF attacks in case you are using cookies to store the JWT. + ## Verifying a JWT using a jwt verification library diff --git a/v2/thirdparty/common-customizations/sessions/with-jwt/read-jwt.mdx b/v2/thirdparty/common-customizations/sessions/with-jwt/read-jwt.mdx index 6406c10f2..22a344607 100644 --- a/v2/thirdparty/common-customizations/sessions/with-jwt/read-jwt.mdx +++ b/v2/thirdparty/common-customizations/sessions/with-jwt/read-jwt.mdx @@ -20,9 +20,12 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Fetching the access token string + + ## On the backend diff --git a/v2/thirdparty/custom-ui/enable-email-verification.mdx b/v2/thirdparty/custom-ui/enable-email-verification.mdx index 028d1a5b0..b56cdf984 100644 --- a/v2/thirdparty/custom-ui/enable-email-verification.mdx +++ b/v2/thirdparty/custom-ui/enable-email-verification.mdx @@ -19,6 +19,7 @@ import {Answer} from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; import TabItem from "@theme/TabItem"; +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; @@ -28,6 +29,8 @@ There are two modes of email verification: - `REQUIRED`: Requires that the user's email is verified before they can access your application's frontend or backend routes (that are protected with a session). - `OPTIONAL`: Adds information about email verification into the session, but leaves it up to you to enforce it on the backend and frontend based on your business logic. + + ## Step 1: Backend setup diff --git a/v2/thirdparty/custom-ui/handling-session-tokens.mdx b/v2/thirdparty/custom-ui/handling-session-tokens.mdx index 5c8cb0294..bdfa1a21f 100644 --- a/v2/thirdparty/custom-ui/handling-session-tokens.mdx +++ b/v2/thirdparty/custom-ui/handling-session-tokens.mdx @@ -9,6 +9,7 @@ hide_title: true import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; @@ -23,6 +24,7 @@ There are two modes ways in which you can use sessions with SuperTokens: Our frontend SDK uses `httpOnly` cookie based session for websites by default as it secures against tokens theft via XSS attacks. For other platform like mobile apps, we use a bearer token in the Authorization header by default. This setting can be changed [as described in the token transfer section](../common-customizations/sessions/token-transfer-method) + ## If using our frontend SDK @@ -41,9 +43,9 @@ Our frontend SDK handles everything for you. You only need to make sure that you Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. -:::note By default, our web SDKs use cookies to provide credentials. -::: + + diff --git a/v2/thirdparty/custom-ui/securing-routes.mdx b/v2/thirdparty/custom-ui/securing-routes.mdx index 00f7e4f61..b33427557 100644 --- a/v2/thirdparty/custom-ui/securing-routes.mdx +++ b/v2/thirdparty/custom-ui/securing-routes.mdx @@ -9,6 +9,7 @@ hide_title: true import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import TabItem from '@theme/TabItem'; import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" @@ -25,6 +26,8 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + + ### Requiring an active session For your APIs that require a user to be logged in, use the `verifySession` middleware @@ -437,7 +440,7 @@ In case of successful session verification, you get access to a `session` object ### Microservice authentication -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/introduction). +For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/unified-login/machine-to-machine-authentication). diff --git a/v2/thirdparty/graphql-integration/backend-setup.mdx b/v2/thirdparty/graphql-integration/backend-setup.mdx index a521f7578..ec91bb6e9 100644 --- a/v2/thirdparty/graphql-integration/backend-setup.mdx +++ b/v2/thirdparty/graphql-integration/backend-setup.mdx @@ -9,11 +9,14 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Backend Setup ## Complete Quick Setup + + @@ -30,6 +33,7 @@ Follow the [frontend and backend custom UI setup guides](../custom-ui/init/front + diff --git a/v2/thirdparty/hasura-integration/with-jwt.mdx b/v2/thirdparty/hasura-integration/with-jwt.mdx index 31e7c262c..e7a2aeb57 100644 --- a/v2/thirdparty/hasura-integration/with-jwt.mdx +++ b/v2/thirdparty/hasura-integration/with-jwt.mdx @@ -23,6 +23,15 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" Using SuperTokens with Hasura requires you to host your own API layer that uses our Backend SDK. If you do not want to host your own server you can use a serverless environment (AWS Lambda for example) to achieve this. ::: +:::caution + +This guide will only work if you are using **SuperTokens Session Tokens**. + +If you are implementing an **OAuth2** based feature, like **Microservice Authentication** or **Unified Login**, the method of adding custom claims is different. +Please check our separate [page](/docs/unified-login/customizations/add-custom-claims-in-tokens) that shows you how to do this. + +::: + ## 1) Complete the setup guides diff --git a/v2/thirdparty/nestjs/guide.mdx b/v2/thirdparty/nestjs/guide.mdx index 585eb5128..3605c8242 100644 --- a/v2/thirdparty/nestjs/guide.mdx +++ b/v2/thirdparty/nestjs/guide.mdx @@ -10,6 +10,7 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" import CoreInjector from "/src/components/coreInjector" import {Question, Answer} from "/src/components/question" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # NestJS Integration Guide @@ -415,6 +416,8 @@ bootstrap(); ## Add a session verification guard + + Now that the library is set up, you can add a guard to protect your API. You can scaffold this by running: `nest g guard auth`. In the newly created `auth.guard.ts` file, implement session verification: diff --git a/v2/thirdparty/nextjs/app-directory/protecting-route.mdx b/v2/thirdparty/nextjs/app-directory/protecting-route.mdx index e636f81f0..311b02a7b 100644 --- a/v2/thirdparty/nextjs/app-directory/protecting-route.mdx +++ b/v2/thirdparty/nextjs/app-directory/protecting-route.mdx @@ -9,16 +9,21 @@ hide_title: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import CoreInjector from "/src/components/coreInjector" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 4. Checking for sessions in frontend routes + + Protecting a website route means that it cannot be accessed unless a user is signed in. If a non signed in user tries to access it, they will be redirected to the login page. + + ## Sessions with Client Components Lets create a client component for the `/` route of our website. diff --git a/v2/thirdparty/nextjs/app-directory/session-verification-middleware.mdx b/v2/thirdparty/nextjs/app-directory/session-verification-middleware.mdx index a76ddce0d..4e4742164 100644 --- a/v2/thirdparty/nextjs/app-directory/session-verification-middleware.mdx +++ b/v2/thirdparty/nextjs/app-directory/session-verification-middleware.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # Using the Next.js middleware + + :::important This method is an alternative method for using sessions in an API. If you are already using [session guards](./session-verification-session-guard.mdx), you can skip this step. ::: diff --git a/v2/thirdparty/nextjs/app-directory/session-verification-session-guard.mdx b/v2/thirdparty/nextjs/app-directory/session-verification-session-guard.mdx index fb03ca4e0..663193a00 100644 --- a/v2/thirdparty/nextjs/app-directory/session-verification-session-guard.mdx +++ b/v2/thirdparty/nextjs/app-directory/session-verification-session-guard.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # Adding a session guard to each API route + + :::note This is applicable for when the frontend calls an API in the `/app/api` folder. ::: diff --git a/v2/thirdparty/nextjs/app-directory/setting-up-backend.mdx b/v2/thirdparty/nextjs/app-directory/setting-up-backend.mdx index b06c3ac05..c90c6f5e5 100644 --- a/v2/thirdparty/nextjs/app-directory/setting-up-backend.mdx +++ b/v2/thirdparty/nextjs/app-directory/setting-up-backend.mdx @@ -11,6 +11,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import AppInfoForm from "/src/components/appInfoForm" + # 3. Adding auth APIs We will add all the backend APIs for auth on `/api/auth`. This can be changed by setting the `apiBasePath` property in the `appInfo` object in the `appInfo.ts` file. For the rest of this page, we will assume you are using `/api/auth`. @@ -37,7 +38,7 @@ import { ensureSuperTokensInit } from '../../../config/backend'; ensureSuperTokensInit(); -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); export async function GET(request: NextRequest) { const res = await handleCall(request); diff --git a/v2/thirdparty/nextjs/protecting-route.mdx b/v2/thirdparty/nextjs/protecting-route.mdx index 21505368e..6de7c2164 100644 --- a/v2/thirdparty/nextjs/protecting-route.mdx +++ b/v2/thirdparty/nextjs/protecting-route.mdx @@ -9,9 +9,12 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import { OAuthFrontendVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # 4. Protecting a website route + + diff --git a/v2/thirdparty/nextjs/session-verification/in-api.mdx b/v2/thirdparty/nextjs/session-verification/in-api.mdx index e0d57c781..182b4d5d2 100644 --- a/v2/thirdparty/nextjs/session-verification/in-api.mdx +++ b/v2/thirdparty/nextjs/session-verification/in-api.mdx @@ -8,9 +8,13 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # 5a. Session verification in an API call + + :::note This is applicable for when the frontend calls an API in the `/pages/api` folder. ::: diff --git a/v2/thirdparty/nextjs/session-verification/in-ssr.mdx b/v2/thirdparty/nextjs/session-verification/in-ssr.mdx index fcdfb1c8d..4ed75df27 100644 --- a/v2/thirdparty/nextjs/session-verification/in-ssr.mdx +++ b/v2/thirdparty/nextjs/session-verification/in-ssr.mdx @@ -10,9 +10,12 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import CoreInjector from "/src/components/coreInjector" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 5b. Session verification in getServerSideProps + + :::note This is applicable for when verifying a session in `getServerSideProps` or `getInitialProps`. ::: diff --git a/v2/thirdparty/pre-built-ui/enable-email-verification.mdx b/v2/thirdparty/pre-built-ui/enable-email-verification.mdx index 61cc6442a..7b4646140 100644 --- a/v2/thirdparty/pre-built-ui/enable-email-verification.mdx +++ b/v2/thirdparty/pre-built-ui/enable-email-verification.mdx @@ -19,6 +19,7 @@ import {Answer} from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; import TabItem from "@theme/TabItem"; +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; @@ -28,6 +29,8 @@ There are two modes of email verification: - `REQUIRED`: Requires that the user's email is verified before they can access your application's frontend or backend routes (that are protected with a session). - `OPTIONAL`: Adds information about email verification into the session, but leaves it up to you to enforce it on the backend and frontend based on your business logic. + + ## Step 1: Backend setup diff --git a/v2/thirdparty/pre-built-ui/further-reading/email-verification.mdx b/v2/thirdparty/pre-built-ui/further-reading/email-verification.mdx index 6ceea4ed2..e05b3ee5a 100644 --- a/v2/thirdparty/pre-built-ui/further-reading/email-verification.mdx +++ b/v2/thirdparty/pre-built-ui/further-reading/email-verification.mdx @@ -7,6 +7,8 @@ hide_title: true +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" + # Email verification There are two modes of email verification: @@ -15,6 +17,8 @@ There are two modes of email verification: ## Information stored in session + + On sign in or sign up, SuperTokens adds information about the email verification status in the session's payload using the `EmailVerificationClaim`. It looks like this: ```json diff --git a/v2/thirdparty/pre-built-ui/handling-session-tokens.mdx b/v2/thirdparty/pre-built-ui/handling-session-tokens.mdx index 05314c1d7..69b80421f 100644 --- a/v2/thirdparty/pre-built-ui/handling-session-tokens.mdx +++ b/v2/thirdparty/pre-built-ui/handling-session-tokens.mdx @@ -10,6 +10,7 @@ hide_title: true import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; @@ -27,9 +28,9 @@ Our frontend SDK handles everything for you. You only need to make sure that you Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. -:::note By default, our web SDKs use cookies to provide credentials. -::: + + diff --git a/v2/thirdparty/pre-built-ui/securing-routes.mdx b/v2/thirdparty/pre-built-ui/securing-routes.mdx index 22d56941e..98c6911b0 100644 --- a/v2/thirdparty/pre-built-ui/securing-routes.mdx +++ b/v2/thirdparty/pre-built-ui/securing-routes.mdx @@ -14,6 +14,7 @@ import TabItem from '@theme/TabItem'; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import {Question, Answer}from "/src/components/question" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" # Securing your API and frontend routes @@ -24,6 +25,8 @@ import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" + + ### Requiring an active session For your APIs that require a user to be logged in, use the `verifySession` middleware @@ -436,7 +439,7 @@ In case of successful session verification, you get access to a `session` object ### Microservice authentication -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/introduction). +For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/unified-login/machine-to-machine-authentication). @@ -446,6 +449,14 @@ For authentication between microservices on your backend, checkout the [microser +:::caution + +These instructions only apply to scenarios in which you are using **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction) with **OAuth2 Access Tokens**, please check the [specific use case page](/docs/unified-login/introduction#when-to-use-unified-login) for relevant information. + +::: + diff --git a/v2/thirdparty/serverless/with-aws-lambda/authorizer.mdx b/v2/thirdparty/serverless/with-aws-lambda/authorizer.mdx index 078cb159b..5f3688425 100644 --- a/v2/thirdparty/serverless/with-aws-lambda/authorizer.mdx +++ b/v2/thirdparty/serverless/with-aws-lambda/authorizer.mdx @@ -10,9 +10,12 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Using Lambda Authorizers + + You can use a lambda as an Authorizer in API Gateways. This will enable you to use SuperTokens in a lambda to authorize requests to other integrations (e.g., AppSync). An Authorizer pointed to this lambda will add `context.authorizer.principalId` that you can map to a header. For example, you can map this to an "x-user-id" header which will be set to the id of the logged-in user. If there is no valid session for the request, this header won't exist. ## 1) Add configurations and dependencies diff --git a/v2/thirdparty/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/thirdparty/serverless/with-aws-lambda/jwt-authorizer.mdx index 86055a1d9..14f128558 100644 --- a/v2/thirdparty/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/thirdparty/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -16,7 +16,14 @@ import TabItem from '@theme/TabItem'; # Using JWT Authorizers :::caution + AWS supports JWT authorizers for HTTP APIs and not REST APIs on the API Gateway service. For REST APIs follow the [Lambda authorizer](./authorizer) guide + +This guide will work if you are using **SuperTokens Session Tokens**. + +If you implementing an **OAuth2** setup, through the [**Unified Login**](/docs/unified-login/introduction) or the [**Microservice Authentication**](/docs/microservice_auth/client-credentials) features, you will have to manually set the token audience property. +Please check the referenced pages for more information. + ::: ## 1) Add the `aud` claim in the JWT based on the authorizer configuration diff --git a/v2/thirdparty/serverless/with-aws-lambda/session-verification.mdx b/v2/thirdparty/serverless/with-aws-lambda/session-verification.mdx index 02857b5e7..ae4954e93 100644 --- a/v2/thirdparty/serverless/with-aws-lambda/session-verification.mdx +++ b/v2/thirdparty/serverless/with-aws-lambda/session-verification.mdx @@ -9,9 +9,12 @@ hide_title: true import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 3. Session verification / Building your APIs + + When building your own APIs, you may need to verify the session of the user before proceeding further. SuperTokens SDK exposes a `verifySession` function that can be utilized for this. In this guide, we will be creating a `/user` `GET` route that will return the current session information. ## 1) Add `/user` `GET` route in your API Gateway diff --git a/v2/thirdparty/serverless/with-netlify/session-verification.mdx b/v2/thirdparty/serverless/with-netlify/session-verification.mdx index 1fd4b1a1f..b28b7cd62 100644 --- a/v2/thirdparty/serverless/with-netlify/session-verification.mdx +++ b/v2/thirdparty/serverless/with-netlify/session-verification.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # 4. Session verification / Building your APIs + + For this guide, we will assume that we want an API `/.netlify/functions/user GET` which returns the current session information. ## 1) Create a new file `netlify/functions/user.js` diff --git a/v2/thirdparty/user-roles/protecting-routes.mdx b/v2/thirdparty/user-roles/protecting-routes.mdx index 9d497d1ca..a970a8ab3 100644 --- a/v2/thirdparty/user-roles/protecting-routes.mdx +++ b/v2/thirdparty/user-roles/protecting-routes.mdx @@ -19,9 +19,19 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting API and frontend routes +:::caution + +This guide applies to scenarios which involve **SuperTokens Session Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), that uses **OAuth2 Access Tokens**, please check our [separate page](/docs/unified-login/customizations/verify-tokens) that shows you how to validate them. +You will have to check for the `roles` claim in the token payload. + +::: + ## Protecting API routes In your API routes you: diff --git a/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx b/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx index 940ec4686..b4763ad81 100644 --- a/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx +++ b/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/general-error.mdx @@ -39,7 +39,7 @@ EmailPassword.init({ return { ...oI, signUpPOST: async function (input) { - let email = input.formFields.find(i => i.id === "email")!.value; + let email = input.formFields.find(i => i.id === "email")!.value as string; if (emailNotAllowed(email)) { // highlight-start diff --git a/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx index 7eef7d4c3..d154a65b7 100644 --- a/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/thirdpartyemailpassword/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -347,7 +347,7 @@ import { backendConfig } from "@/app/config/backend"; SuperTokens.init(backendConfig()); // in the app/api/auth/[...path]/route.ts file -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); const withCustomErrorHandling = async (request: NextRequest) => { try { @@ -485,4 +485,4 @@ class ErrorHandlerMiddleware: - \ No newline at end of file + diff --git a/v2/thirdpartyemailpassword/common-customizations/account-linking/manual-account-linking.mdx b/v2/thirdpartyemailpassword/common-customizations/account-linking/manual-account-linking.mdx index e0e20ec04..14e06af8f 100644 --- a/v2/thirdpartyemailpassword/common-customizations/account-linking/manual-account-linking.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/account-linking/manual-account-linking.mdx @@ -300,9 +300,11 @@ def link_accounts_helper(primary_user_id: str, recipe_user_id: RecipeUserId): - + + + ## Unlinking accounts If you want to unlink an account from its primary user ID, you can use the following function: @@ -468,4 +470,4 @@ Our SDK also exposes several other helper functions: - `AccountLinking.isEmailChangeAllowed`: Given the recipe user id and the new email for update, this function returns `true` if it's safe to update the email, else `false`. Below are the conditions in which `false` is returned: - If the input recipe user is a primary user, then we need to check that the new email doesn't belong to any other primary user. If it does, we disallow the change since multiple primary user's can't have the same email. - - If the recipe user is NOT a primary user, and if the new email is not verified, then we check if there exists a primary user with the same email, and if it exists, we disallow the email change. We disallow because if this email is changed, and an email verification email is sent, then the primary user may end up clicking on the link by mistake, causing account linking to happen which can result in account take over if this recipe user is malicious. \ No newline at end of file + - If the recipe user is NOT a primary user, and if the new email is not verified, then we check if there exists a primary user with the same email, and if it exists, we disallow the email change. We disallow because if this email is changed, and an email verification email is sent, then the primary user may end up clicking on the link by mistake, causing account linking to happen which can result in account take over if this recipe user is malicious. diff --git a/v2/thirdpartyemailpassword/common-customizations/email-verification/about.mdx b/v2/thirdpartyemailpassword/common-customizations/email-verification/about.mdx index 63b5e5ff1..ceb8fd555 100644 --- a/v2/thirdpartyemailpassword/common-customizations/email-verification/about.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/email-verification/about.mdx @@ -17,6 +17,7 @@ import TabItem from "@theme/TabItem"; import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import AngularUIImplementation from "/src/components/reusableSnippets/angularUIImplementation" import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Enable email verification @@ -25,6 +26,8 @@ import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplement Email verification is turned off by default. It is strongly encouraged to enable it to ensure the authenticity of your users. ::: + + diff --git a/v2/thirdpartyemailpassword/common-customizations/email-verification/protecting-routes.mdx b/v2/thirdpartyemailpassword/common-customizations/email-verification/protecting-routes.mdx index b8759e741..6e13b1a87 100644 --- a/v2/thirdpartyemailpassword/common-customizations/email-verification/protecting-routes.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/email-verification/protecting-routes.mdx @@ -24,11 +24,14 @@ import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import AngularUIImplementation from "/src/components/reusableSnippets/angularUIImplementation" import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" import {Answer} from "/src/components/question" +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting backend APIs and website routes ## Protecting backend API routes + + ### Add email verification checks to all API routes If you want to protect all your backend API routes with email verification checks, set the `mode` to `REQUIRED` in the `EmailVerification` config. Routes protected with the `verifySession` middleware will now additionally check for email verification status. diff --git a/v2/thirdpartyemailpassword/common-customizations/handling-signinup-success.mdx b/v2/thirdpartyemailpassword/common-customizations/handling-signinup-success.mdx index 4a7ea85ed..b7043794a 100644 --- a/v2/thirdpartyemailpassword/common-customizations/handling-signinup-success.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/handling-signinup-success.mdx @@ -595,7 +595,7 @@ SuperTokens.init({ let name = "" for (let i = 0; i < input.formFields.length; i++) { if (input.formFields[i].id == "name") { - name = input.formFields[i].value + name = input.formFields[i].value as string; } } @@ -777,4 +777,4 @@ For example, when the frontend calls the email password sign up API, the SDK inv Therefore, if you want to associate a role with a user, you would want to do that in the `Functions.signUp` function since then those roles would get added to the session in the subsequent call to the `Session.createNewSession` function. If instead, you associate a role to the user in the `API.signUpPOST` (after calling the original implementation), the role will not be automatically added to the session since the `Session.createNewSession` would have already been called before the original implementation returns. -The only time it makes sense to override the API functions is if you want to access an argument that's not available in the recipe function. For example, the custom form fields for email password sign up is an input to the `API.signUpPOST`, but not to the `Functions.signUp`, so if you want to access the form fields, you should override the `API.signUpPOST` as shown above. You can also always add the formFields to the userContext object and read it later in the `Functions.signUp` override, but then you would lose the typing of the form field array structure (which is not a runtime problem, but just a slightly bad developer experience). \ No newline at end of file +The only time it makes sense to override the API functions is if you want to access an argument that's not available in the recipe function. For example, the custom form fields for email password sign up is an input to the `API.signUpPOST`, but not to the `Functions.signUp`, so if you want to access the form fields, you should override the `API.signUpPOST` as shown above. You can also always add the formFields to the userContext object and read it later in the `Functions.signUp` override, but then you would lose the typing of the form field array structure (which is not a runtime problem, but just a slightly bad developer experience). diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/claims/access-token-payload.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/claims/access-token-payload.mdx index 3563fef3a..f0290e6cf 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/claims/access-token-payload.mdx @@ -22,6 +22,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 1. Using the access token payload +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## Add custom claims to the access token payload :::important diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/claims/claim-validators.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/claims/claim-validators.mdx index fdc63bbb1..21f55d69d 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/claims/claim-validators.mdx @@ -23,6 +23,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 2. Using claim validators +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## What are session claims? SuperTokens session has a property called `accessTokenPayload`. This is a `JSON` object that's stored in a user's session which can be accessed on the frontend and backend. The key-values in this JSON payload are called claims. diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/protecting-frontend-routes.mdx index 232bba79f..9e5719247 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/protecting-frontend-routes.mdx @@ -14,9 +14,12 @@ import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthFrontendVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting frontend routes + + diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/session-verification-in-api/get-session.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/session-verification-in-api/get-session.mdx index 94a21fa6d..ce8decce4 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/session-verification-in-api/get-session.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/session-verification-in-api/get-session.mdx @@ -13,10 +13,13 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session Verification using `getSession` + + If you want to use a non-middleware form of `verifySession`, you can use the `getSession` function. ## Using `getSession` diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/session-verification-in-api/verify-session.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/session-verification-in-api/verify-session.mdx index f176eab66..e7a1df653 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/session-verification-in-api/verify-session.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/session-verification-in-api/verify-session.mdx @@ -10,10 +10,13 @@ import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" ## Verifying a session using the `verifySession` middleware + + For your APIs that require a user to be logged in, use the `verifySession` middleware: diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/ssr.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/ssr.mdx index 863e5cb5b..e89210f16 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/ssr.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/ssr.mdx @@ -17,9 +17,12 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session verification during server side rendering + + :::important Getting access to the session during server side rendering is only possible using cookie-based sessions. This is the default setting, but you have to keep this in mind if you want to switch to header-based sessions. ::: diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/with-jwt/jwt-verification.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/with-jwt/jwt-verification.mdx index d1d014c9c..c39494207 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/with-jwt/jwt-verification.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/with-jwt/jwt-verification.mdx @@ -15,14 +15,18 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import AppInfoForm from "/src/components/appInfoForm"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Manually verify the JWT + + There are three steps in doing session verification using JWTs: - Verify the JWT signature and expiry using a JWT verification library - Check for custom claim values for authorization. - Preventing CSRF attacks in case you are using cookies to store the JWT. + ## Verifying a JWT using a jwt verification library diff --git a/v2/thirdpartyemailpassword/common-customizations/sessions/with-jwt/read-jwt.mdx b/v2/thirdpartyemailpassword/common-customizations/sessions/with-jwt/read-jwt.mdx index 6406c10f2..22a344607 100644 --- a/v2/thirdpartyemailpassword/common-customizations/sessions/with-jwt/read-jwt.mdx +++ b/v2/thirdpartyemailpassword/common-customizations/sessions/with-jwt/read-jwt.mdx @@ -20,9 +20,12 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Fetching the access token string + + ## On the backend diff --git a/v2/thirdpartyemailpassword/custom-ui/enable-email-verification.mdx b/v2/thirdpartyemailpassword/custom-ui/enable-email-verification.mdx index 028d1a5b0..b56cdf984 100644 --- a/v2/thirdpartyemailpassword/custom-ui/enable-email-verification.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/enable-email-verification.mdx @@ -19,6 +19,7 @@ import {Answer} from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; import TabItem from "@theme/TabItem"; +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; @@ -28,6 +29,8 @@ There are two modes of email verification: - `REQUIRED`: Requires that the user's email is verified before they can access your application's frontend or backend routes (that are protected with a session). - `OPTIONAL`: Adds information about email verification into the session, but leaves it up to you to enforce it on the backend and frontend based on your business logic. + + ## Step 1: Backend setup diff --git a/v2/thirdpartyemailpassword/custom-ui/handling-session-tokens.mdx b/v2/thirdpartyemailpassword/custom-ui/handling-session-tokens.mdx index 5c8cb0294..bdfa1a21f 100644 --- a/v2/thirdpartyemailpassword/custom-ui/handling-session-tokens.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/handling-session-tokens.mdx @@ -9,6 +9,7 @@ hide_title: true import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; @@ -23,6 +24,7 @@ There are two modes ways in which you can use sessions with SuperTokens: Our frontend SDK uses `httpOnly` cookie based session for websites by default as it secures against tokens theft via XSS attacks. For other platform like mobile apps, we use a bearer token in the Authorization header by default. This setting can be changed [as described in the token transfer section](../common-customizations/sessions/token-transfer-method) + ## If using our frontend SDK @@ -41,9 +43,9 @@ Our frontend SDK handles everything for you. You only need to make sure that you Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. -:::note By default, our web SDKs use cookies to provide credentials. -::: + + diff --git a/v2/thirdpartyemailpassword/custom-ui/securing-routes.mdx b/v2/thirdpartyemailpassword/custom-ui/securing-routes.mdx index e7b03dcdb..b33427557 100644 --- a/v2/thirdpartyemailpassword/custom-ui/securing-routes.mdx +++ b/v2/thirdpartyemailpassword/custom-ui/securing-routes.mdx @@ -9,6 +9,7 @@ hide_title: true import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import TabItem from '@theme/TabItem'; import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" @@ -25,6 +26,8 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + + ### Requiring an active session For your APIs that require a user to be logged in, use the `verifySession` middleware @@ -437,7 +440,7 @@ In case of successful session verification, you get access to a `session` object ### Microservice authentication -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/introduction). +For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/unified-login/machine-to-machine-authentication). @@ -587,4 +590,4 @@ Future doesSessionExist() async { - [Changing session lifetime](../common-customizations/sessions/change-session-timeout) - [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - \ No newline at end of file + diff --git a/v2/thirdpartyemailpassword/graphql-integration/backend-setup.mdx b/v2/thirdpartyemailpassword/graphql-integration/backend-setup.mdx index 607112fce..ec91bb6e9 100644 --- a/v2/thirdpartyemailpassword/graphql-integration/backend-setup.mdx +++ b/v2/thirdpartyemailpassword/graphql-integration/backend-setup.mdx @@ -9,11 +9,14 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Backend Setup ## Complete Quick Setup + + @@ -30,6 +33,7 @@ Follow the [frontend and backend custom UI setup guides](../custom-ui/init/front + @@ -201,4 +205,4 @@ const server = new ApolloServer({ }) ``` - \ No newline at end of file + diff --git a/v2/thirdpartyemailpassword/hasura-integration/with-jwt.mdx b/v2/thirdpartyemailpassword/hasura-integration/with-jwt.mdx index 16bcb21fe..e7a2aeb57 100644 --- a/v2/thirdpartyemailpassword/hasura-integration/with-jwt.mdx +++ b/v2/thirdpartyemailpassword/hasura-integration/with-jwt.mdx @@ -23,6 +23,15 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" Using SuperTokens with Hasura requires you to host your own API layer that uses our Backend SDK. If you do not want to host your own server you can use a serverless environment (AWS Lambda for example) to achieve this. ::: +:::caution + +This guide will only work if you are using **SuperTokens Session Tokens**. + +If you are implementing an **OAuth2** based feature, like **Microservice Authentication** or **Unified Login**, the method of adding custom claims is different. +Please check our separate [page](/docs/unified-login/customizations/add-custom-claims-in-tokens) that shows you how to do this. + +::: + ## 1) Complete the setup guides @@ -443,4 +452,4 @@ If you are using Hasura cloud and testing your backend APIs in your local enviro To solve this problem you will need to expose your locally hosted backend APIs to the internet. For example you can use [ngrok](https://ngrok.com/). After that, you need to configure Hasura to use the `{ngrokURL}/{apiBasePath}/jwt/jwks.json` as the JWKS endpoint (explained in [step 4](#4-configure-hasura-environment-variables)) - \ No newline at end of file + diff --git a/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx b/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx index fc23b4a1a..cfeeac4a0 100644 --- a/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx +++ b/v2/thirdpartyemailpassword/migration/account-creation/ep-migration-without-password-hash.mdx @@ -50,7 +50,7 @@ EmailPassword.init({ return { ...originalImplementation, signUpPOST: async function (input) { - let email = input.formFields.find((field) => field.id === "email")!.value; + let email = input.formFields.find((field) => field.id === "email")!.value as string; // Check if the user signing in exists in the external provider if (await doesUserExistInExternalProvider(email)) { // Return status "EMAIL_ALREADY_EXISTS_ERROR" since the user already exists in the external provider @@ -223,8 +223,8 @@ EmailPassword.init({ ...originalImplementation, signInPOST: async function (input) { // Check if an email-password user with the input email exists in SuperTokens - let email = input.formFields.find((field) => field.id === "email")!.value; - let password = input.formFields.find((field) => field.id === "password")!.value; + let email = input.formFields.find((field) => field.id === "email")!.value as string; + let password = input.formFields.find((field) => field.id === "password")!.value as string; let supertokensUsersWithSameEmail = await SuperTokens.listUsersByAccountInfo(input.tenantId, { email: email }, undefined, input.userContext); @@ -582,7 +582,7 @@ EmailPassword.init({ // Add overrides from the previous step generatePasswordResetTokenPOST: async (input) => { // Retrieve the email from the input - let email = input.formFields.find(i => i.id === "email")!.value; + let email = input.formFields.find(i => i.id === "email")!.value as string; // check if user exists in SuperTokens let supertokensUsersWithSameEmail = await SuperTokens.listUsersByAccountInfo(input.tenantId, { @@ -1133,8 +1133,8 @@ EmailPassword.init({ ...originalImplementation, signInPOST: async function (input) { // Check if an email-password user with the input email exists in SuperTokens - let email = input.formFields.find((field) => field.id === "email")!.value; - let password = input.formFields.find((field) => field.id === "password")!.value; + let email = input.formFields.find((field) => field.id === "email")!.value as string; + let password = input.formFields.find((field) => field.id === "password")!.value as string; let supertokensUsersWithSameEmail = await SuperTokens.listUsersByAccountInfo(input.tenantId, { email: email }, undefined, input.userContext); @@ -1594,4 +1594,4 @@ Your migration period could be decided by either of the following factors: After the migration period ends you have to make the following changes to stop automatic user migration: - Remove all migration related override changes in your backend. - Take the remaining users' emails and call the signup function with a secure randomized password. -- Email users encouraging them to go through the password reset flow. \ No newline at end of file +- Email users encouraging them to go through the password reset flow. diff --git a/v2/thirdpartyemailpassword/nestjs/guide.mdx b/v2/thirdpartyemailpassword/nestjs/guide.mdx index bcd6f9e4e..f30bf6ee6 100644 --- a/v2/thirdpartyemailpassword/nestjs/guide.mdx +++ b/v2/thirdpartyemailpassword/nestjs/guide.mdx @@ -10,6 +10,7 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" import CoreInjector from "/src/components/coreInjector" import {Question, Answer} from "/src/components/question" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # NestJS Integration Guide @@ -417,6 +418,8 @@ bootstrap(); ## Add a session verification guard + + Now that the library is set up, you can add a guard to protect your API. You can scaffold this by running: `nest g guard auth`. In the newly created `auth.guard.ts` file, implement session verification: @@ -520,4 +523,4 @@ You have successfully completed the quick setup! Head over to the "Post login op - \ No newline at end of file + diff --git a/v2/thirdpartyemailpassword/nextjs/app-directory/protecting-route.mdx b/v2/thirdpartyemailpassword/nextjs/app-directory/protecting-route.mdx index 062a948d2..037bb6e93 100644 --- a/v2/thirdpartyemailpassword/nextjs/app-directory/protecting-route.mdx +++ b/v2/thirdpartyemailpassword/nextjs/app-directory/protecting-route.mdx @@ -10,16 +10,21 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import CoreInjector from "/src/components/coreInjector" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 4. Checking for sessions in frontend routes + + Protecting a website route means that it cannot be accessed unless a user is signed in. If a non signed in user tries to access it, they will be redirected to the login page. + + ## Sessions with Client Components Lets create a client component for the `/` route of our website. @@ -504,4 +509,4 @@ For custom UI SuperTokens provides no login UI, the code above will redirect the - \ No newline at end of file + diff --git a/v2/thirdpartyemailpassword/nextjs/app-directory/session-verification-middleware.mdx b/v2/thirdpartyemailpassword/nextjs/app-directory/session-verification-middleware.mdx index f13fd3389..4e4742164 100644 --- a/v2/thirdpartyemailpassword/nextjs/app-directory/session-verification-middleware.mdx +++ b/v2/thirdpartyemailpassword/nextjs/app-directory/session-verification-middleware.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # Using the Next.js middleware + + :::important This method is an alternative method for using sessions in an API. If you are already using [session guards](./session-verification-session-guard.mdx), you can skip this step. ::: @@ -93,4 +97,4 @@ export function GET(request: NextRequest) { } ``` -This creates a `GET` request for the `/api/userid` route which returns the user id of the currently logged in user. \ No newline at end of file +This creates a `GET` request for the `/api/userid` route which returns the user id of the currently logged in user. diff --git a/v2/thirdpartyemailpassword/nextjs/app-directory/session-verification-session-guard.mdx b/v2/thirdpartyemailpassword/nextjs/app-directory/session-verification-session-guard.mdx index 3900bb842..663193a00 100644 --- a/v2/thirdpartyemailpassword/nextjs/app-directory/session-verification-session-guard.mdx +++ b/v2/thirdpartyemailpassword/nextjs/app-directory/session-verification-session-guard.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # Adding a session guard to each API route + + :::note This is applicable for when the frontend calls an API in the `/app/api` folder. ::: @@ -50,4 +54,4 @@ In the above snippet we are creating a `GET` handler for the `/api/user` route. The `withSession` guard will return: - Status `401` if the session does not exist or has expired -- Stauts `403` if the session claims fail their validation. For example if email verification is required but the user's email is not verified. \ No newline at end of file +- Stauts `403` if the session claims fail their validation. For example if email verification is required but the user's email is not verified. diff --git a/v2/thirdpartyemailpassword/nextjs/app-directory/setting-up-backend.mdx b/v2/thirdpartyemailpassword/nextjs/app-directory/setting-up-backend.mdx index b06c3ac05..c90c6f5e5 100644 --- a/v2/thirdpartyemailpassword/nextjs/app-directory/setting-up-backend.mdx +++ b/v2/thirdpartyemailpassword/nextjs/app-directory/setting-up-backend.mdx @@ -11,6 +11,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import AppInfoForm from "/src/components/appInfoForm" + # 3. Adding auth APIs We will add all the backend APIs for auth on `/api/auth`. This can be changed by setting the `apiBasePath` property in the `appInfo` object in the `appInfo.ts` file. For the rest of this page, we will assume you are using `/api/auth`. @@ -37,7 +38,7 @@ import { ensureSuperTokensInit } from '../../../config/backend'; ensureSuperTokensInit(); -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); export async function GET(request: NextRequest) { const res = await handleCall(request); diff --git a/v2/thirdpartyemailpassword/nextjs/protecting-route.mdx b/v2/thirdpartyemailpassword/nextjs/protecting-route.mdx index adb5b3dcb..6de7c2164 100644 --- a/v2/thirdpartyemailpassword/nextjs/protecting-route.mdx +++ b/v2/thirdpartyemailpassword/nextjs/protecting-route.mdx @@ -9,9 +9,12 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import { OAuthFrontendVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # 4. Protecting a website route + + @@ -64,4 +67,4 @@ async function doesSessionExist() { - \ No newline at end of file + diff --git a/v2/thirdpartyemailpassword/nextjs/session-verification/in-api.mdx b/v2/thirdpartyemailpassword/nextjs/session-verification/in-api.mdx index e0d57c781..182b4d5d2 100644 --- a/v2/thirdpartyemailpassword/nextjs/session-verification/in-api.mdx +++ b/v2/thirdpartyemailpassword/nextjs/session-verification/in-api.mdx @@ -8,9 +8,13 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # 5a. Session verification in an API call + + :::note This is applicable for when the frontend calls an API in the `/pages/api` folder. ::: diff --git a/v2/thirdpartyemailpassword/nextjs/session-verification/in-ssr.mdx b/v2/thirdpartyemailpassword/nextjs/session-verification/in-ssr.mdx index fcdfb1c8d..4ed75df27 100644 --- a/v2/thirdpartyemailpassword/nextjs/session-verification/in-ssr.mdx +++ b/v2/thirdpartyemailpassword/nextjs/session-verification/in-ssr.mdx @@ -10,9 +10,12 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import CoreInjector from "/src/components/coreInjector" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 5b. Session verification in getServerSideProps + + :::note This is applicable for when verifying a session in `getServerSideProps` or `getInitialProps`. ::: diff --git a/v2/thirdpartyemailpassword/pre-built-ui/enable-email-verification.mdx b/v2/thirdpartyemailpassword/pre-built-ui/enable-email-verification.mdx index 61cc6442a..3b5ca11e8 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/enable-email-verification.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/enable-email-verification.mdx @@ -19,6 +19,7 @@ import {Answer} from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; import TabItem from "@theme/TabItem"; +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; @@ -28,6 +29,8 @@ There are two modes of email verification: - `REQUIRED`: Requires that the user's email is verified before they can access your application's frontend or backend routes (that are protected with a session). - `OPTIONAL`: Adds information about email verification into the session, but leaves it up to you to enforce it on the backend and frontend based on your business logic. + + ## Step 1: Backend setup @@ -811,4 +814,4 @@ In your protected routes, you need to first check if a session exists, and then - Learn more about [session claim validators](../common-customizations/sessions/claims/claim-validators) and about [how to read the email verification status from the session payload](../common-customizations/email-verification/protecting-routes) - Exclude email verification check [in certain APIs](../common-customizations/email-verification/protecting-routes) or [certain frontend routes](../common-customizations/email-verification/protecting-routes#protecting-frontend-routes) in `REQUIRED` mode. - \ No newline at end of file + diff --git a/v2/thirdpartyemailpassword/pre-built-ui/further-reading/email-verification.mdx b/v2/thirdpartyemailpassword/pre-built-ui/further-reading/email-verification.mdx index 9cee023e8..e05b3ee5a 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/further-reading/email-verification.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/further-reading/email-verification.mdx @@ -7,6 +7,8 @@ hide_title: true +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" + # Email verification There are two modes of email verification: @@ -15,6 +17,8 @@ There are two modes of email verification: ## Information stored in session + + On sign in or sign up, SuperTokens adds information about the email verification status in the session's payload using the `EmailVerificationClaim`. It looks like this: ```json @@ -83,4 +87,4 @@ The default email we send for email verification is shown below. It is sent via ## See also -- [Enabling email verification](../enable-email-verification) \ No newline at end of file +- [Enabling email verification](../enable-email-verification) diff --git a/v2/thirdpartyemailpassword/pre-built-ui/handling-session-tokens.mdx b/v2/thirdpartyemailpassword/pre-built-ui/handling-session-tokens.mdx index 05314c1d7..69b80421f 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/handling-session-tokens.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/handling-session-tokens.mdx @@ -10,6 +10,7 @@ hide_title: true import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; @@ -27,9 +28,9 @@ Our frontend SDK handles everything for you. You only need to make sure that you Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. -:::note By default, our web SDKs use cookies to provide credentials. -::: + + diff --git a/v2/thirdpartyemailpassword/pre-built-ui/securing-routes.mdx b/v2/thirdpartyemailpassword/pre-built-ui/securing-routes.mdx index c4eece720..98c6911b0 100644 --- a/v2/thirdpartyemailpassword/pre-built-ui/securing-routes.mdx +++ b/v2/thirdpartyemailpassword/pre-built-ui/securing-routes.mdx @@ -14,6 +14,7 @@ import TabItem from '@theme/TabItem'; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import {Question, Answer}from "/src/components/question" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" # Securing your API and frontend routes @@ -24,6 +25,8 @@ import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" + + ### Requiring an active session For your APIs that require a user to be logged in, use the `verifySession` middleware @@ -436,7 +439,7 @@ In case of successful session verification, you get access to a `session` object ### Microservice authentication -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/introduction). +For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/unified-login/machine-to-machine-authentication). @@ -446,6 +449,14 @@ For authentication between microservices on your backend, checkout the [microser +:::caution + +These instructions only apply to scenarios in which you are using **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction) with **OAuth2 Access Tokens**, please check the [specific use case page](/docs/unified-login/introduction#when-to-use-unified-login) for relevant information. + +::: + @@ -519,4 +530,4 @@ async function doesSessionExist() { - [Changing session lifetime](../common-customizations/sessions/change-session-timeout) - [Sharing session across sub domains](../common-customizations/sessions/share-sessions-across-sub-domains) - \ No newline at end of file + diff --git a/v2/thirdpartyemailpassword/serverless/with-aws-lambda/authorizer.mdx b/v2/thirdpartyemailpassword/serverless/with-aws-lambda/authorizer.mdx index 078cb159b..5f3688425 100644 --- a/v2/thirdpartyemailpassword/serverless/with-aws-lambda/authorizer.mdx +++ b/v2/thirdpartyemailpassword/serverless/with-aws-lambda/authorizer.mdx @@ -10,9 +10,12 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Using Lambda Authorizers + + You can use a lambda as an Authorizer in API Gateways. This will enable you to use SuperTokens in a lambda to authorize requests to other integrations (e.g., AppSync). An Authorizer pointed to this lambda will add `context.authorizer.principalId` that you can map to a header. For example, you can map this to an "x-user-id" header which will be set to the id of the logged-in user. If there is no valid session for the request, this header won't exist. ## 1) Add configurations and dependencies diff --git a/v2/thirdpartyemailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/thirdpartyemailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx index 86055a1d9..14f128558 100644 --- a/v2/thirdpartyemailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/thirdpartyemailpassword/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -16,7 +16,14 @@ import TabItem from '@theme/TabItem'; # Using JWT Authorizers :::caution + AWS supports JWT authorizers for HTTP APIs and not REST APIs on the API Gateway service. For REST APIs follow the [Lambda authorizer](./authorizer) guide + +This guide will work if you are using **SuperTokens Session Tokens**. + +If you implementing an **OAuth2** setup, through the [**Unified Login**](/docs/unified-login/introduction) or the [**Microservice Authentication**](/docs/microservice_auth/client-credentials) features, you will have to manually set the token audience property. +Please check the referenced pages for more information. + ::: ## 1) Add the `aud` claim in the JWT based on the authorizer configuration diff --git a/v2/thirdpartyemailpassword/serverless/with-aws-lambda/session-verification.mdx b/v2/thirdpartyemailpassword/serverless/with-aws-lambda/session-verification.mdx index e5b2f994a..ae4954e93 100644 --- a/v2/thirdpartyemailpassword/serverless/with-aws-lambda/session-verification.mdx +++ b/v2/thirdpartyemailpassword/serverless/with-aws-lambda/session-verification.mdx @@ -9,9 +9,12 @@ hide_title: true import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 3. Session verification / Building your APIs + + When building your own APIs, you may need to verify the session of the user before proceeding further. SuperTokens SDK exposes a `verifySession` function that can be utilized for this. In this guide, we will be creating a `/user` `GET` route that will return the current session information. ## 1) Add `/user` `GET` route in your API Gateway @@ -178,4 +181,4 @@ If each API route has its own lambda function, you can skip using the SuperToken - \ No newline at end of file + diff --git a/v2/thirdpartyemailpassword/serverless/with-netlify/session-verification.mdx b/v2/thirdpartyemailpassword/serverless/with-netlify/session-verification.mdx index 1fd4b1a1f..b28b7cd62 100644 --- a/v2/thirdpartyemailpassword/serverless/with-netlify/session-verification.mdx +++ b/v2/thirdpartyemailpassword/serverless/with-netlify/session-verification.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # 4. Session verification / Building your APIs + + For this guide, we will assume that we want an API `/.netlify/functions/user GET` which returns the current session information. ## 1) Create a new file `netlify/functions/user.js` diff --git a/v2/thirdpartyemailpassword/user-roles/protecting-routes.mdx b/v2/thirdpartyemailpassword/user-roles/protecting-routes.mdx index 9d497d1ca..a970a8ab3 100644 --- a/v2/thirdpartyemailpassword/user-roles/protecting-routes.mdx +++ b/v2/thirdpartyemailpassword/user-roles/protecting-routes.mdx @@ -19,9 +19,19 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting API and frontend routes +:::caution + +This guide applies to scenarios which involve **SuperTokens Session Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), that uses **OAuth2 Access Tokens**, please check our [separate page](/docs/unified-login/customizations/verify-tokens) that shows you how to validate them. +You will have to check for the `roles` claim in the token payload. + +::: + ## Protecting API routes In your API routes you: diff --git a/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx b/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx index fd453819d..d154a65b7 100644 --- a/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx +++ b/v2/thirdpartypasswordless/advanced-customizations/apis-override/custom-response/throwing-error.mdx @@ -347,7 +347,7 @@ import { backendConfig } from "@/app/config/backend"; SuperTokens.init(backendConfig()); // in the app/api/auth/[...path]/route.ts file -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); const withCustomErrorHandling = async (request: NextRequest) => { try { diff --git a/v2/thirdpartypasswordless/common-customizations/account-linking/manual-account-linking.mdx b/v2/thirdpartypasswordless/common-customizations/account-linking/manual-account-linking.mdx index 1efedbddc..14e06af8f 100644 --- a/v2/thirdpartypasswordless/common-customizations/account-linking/manual-account-linking.mdx +++ b/v2/thirdpartypasswordless/common-customizations/account-linking/manual-account-linking.mdx @@ -300,9 +300,11 @@ def link_accounts_helper(primary_user_id: str, recipe_user_id: RecipeUserId): - + + + ## Unlinking accounts If you want to unlink an account from its primary user ID, you can use the following function: diff --git a/v2/thirdpartypasswordless/common-customizations/email-verification/about.mdx b/v2/thirdpartypasswordless/common-customizations/email-verification/about.mdx index 63b5e5ff1..ceb8fd555 100644 --- a/v2/thirdpartypasswordless/common-customizations/email-verification/about.mdx +++ b/v2/thirdpartypasswordless/common-customizations/email-verification/about.mdx @@ -17,6 +17,7 @@ import TabItem from "@theme/TabItem"; import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import AngularUIImplementation from "/src/components/reusableSnippets/angularUIImplementation" import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Enable email verification @@ -25,6 +26,8 @@ import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplement Email verification is turned off by default. It is strongly encouraged to enable it to ensure the authenticity of your users. ::: + + diff --git a/v2/thirdpartypasswordless/common-customizations/email-verification/protecting-routes.mdx b/v2/thirdpartypasswordless/common-customizations/email-verification/protecting-routes.mdx index b8759e741..6e13b1a87 100644 --- a/v2/thirdpartypasswordless/common-customizations/email-verification/protecting-routes.mdx +++ b/v2/thirdpartypasswordless/common-customizations/email-verification/protecting-routes.mdx @@ -24,11 +24,14 @@ import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import AngularUIImplementation from "/src/components/reusableSnippets/angularUIImplementation" import VueUIImplementation from "/src/components/reusableSnippets/vueUIImplementation" import {Answer} from "/src/components/question" +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting backend APIs and website routes ## Protecting backend API routes + + ### Add email verification checks to all API routes If you want to protect all your backend API routes with email verification checks, set the `mode` to `REQUIRED` in the `EmailVerification` config. Routes protected with the `verifySession` middleware will now additionally check for email verification status. diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/claims/access-token-payload.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/claims/access-token-payload.mdx index 3563fef3a..f0290e6cf 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/claims/access-token-payload.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/claims/access-token-payload.mdx @@ -22,6 +22,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 1. Using the access token payload +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## Add custom claims to the access token payload :::important diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/claims/claim-validators.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/claims/claim-validators.mdx index fdc63bbb1..21f55d69d 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/claims/claim-validators.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/claims/claim-validators.mdx @@ -23,6 +23,14 @@ import FrontendReactContextSubTabs from "/src/components/tabs/FrontendReactConte # Option 2. Using claim validators +:::caution + +This guide describes how to add custom claims to **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), which makes use of **OAuth2 Access Tokens**, you will have to check the separate [guide](/docs/unified-login/customizations/add-custom-claims-in-tokens). + +::: + ## What are session claims? SuperTokens session has a property called `accessTokenPayload`. This is a `JSON` object that's stored in a user's session which can be accessed on the frontend and backend. The key-values in this JSON payload are called claims. diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/protecting-frontend-routes.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/protecting-frontend-routes.mdx index 232bba79f..9e5719247 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/protecting-frontend-routes.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/protecting-frontend-routes.mdx @@ -14,9 +14,12 @@ import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthFrontendVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting frontend routes + + diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/session-verification-in-api/get-session.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/session-verification-in-api/get-session.mdx index 94a21fa6d..ce8decce4 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/session-verification-in-api/get-session.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/session-verification-in-api/get-session.mdx @@ -13,10 +13,13 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session Verification using `getSession` + + If you want to use a non-middleware form of `verifySession`, you can use the `getSession` function. ## Using `getSession` diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/session-verification-in-api/verify-session.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/session-verification-in-api/verify-session.mdx index f176eab66..e7a1df653 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/session-verification-in-api/verify-session.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/session-verification-in-api/verify-session.mdx @@ -10,10 +10,13 @@ import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" ## Verifying a session using the `verifySession` middleware + + For your APIs that require a user to be logged in, use the `verifySession` middleware: diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/ssr.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/ssr.mdx index 863e5cb5b..e89210f16 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/ssr.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/ssr.mdx @@ -17,9 +17,12 @@ import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import PythonSyncAsyncSubTabs from "/src/components/tabs/PythonSyncAsyncSubTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Session verification during server side rendering + + :::important Getting access to the session during server side rendering is only possible using cookie-based sessions. This is the default setting, but you have to keep this in mind if you want to switch to header-based sessions. ::: diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/with-jwt/jwt-verification.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/with-jwt/jwt-verification.mdx index d1d014c9c..c39494207 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/with-jwt/jwt-verification.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/with-jwt/jwt-verification.mdx @@ -15,14 +15,18 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import AppInfoForm from "/src/components/appInfoForm"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Manually verify the JWT + + There are three steps in doing session verification using JWTs: - Verify the JWT signature and expiry using a JWT verification library - Check for custom claim values for authorization. - Preventing CSRF attacks in case you are using cookies to store the JWT. + ## Verifying a JWT using a jwt verification library diff --git a/v2/thirdpartypasswordless/common-customizations/sessions/with-jwt/read-jwt.mdx b/v2/thirdpartypasswordless/common-customizations/sessions/with-jwt/read-jwt.mdx index 6406c10f2..22a344607 100644 --- a/v2/thirdpartypasswordless/common-customizations/sessions/with-jwt/read-jwt.mdx +++ b/v2/thirdpartypasswordless/common-customizations/sessions/with-jwt/read-jwt.mdx @@ -20,9 +20,12 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Fetching the access token string + + ## On the backend diff --git a/v2/thirdpartypasswordless/custom-ui/enable-email-verification.mdx b/v2/thirdpartypasswordless/custom-ui/enable-email-verification.mdx index 7fa80285a..f5e1e35e5 100644 --- a/v2/thirdpartypasswordless/custom-ui/enable-email-verification.mdx +++ b/v2/thirdpartypasswordless/custom-ui/enable-email-verification.mdx @@ -20,6 +20,7 @@ import {Answer} from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; import TabItem from "@theme/TabItem"; +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; @@ -29,6 +30,8 @@ There are two modes of email verification: - `REQUIRED`: Requires that the user's email is verified before they can access your application's frontend or backend routes (that are protected with a session). - `OPTIONAL`: Adds information about email verification into the session, but leaves it up to you to enforce it on the backend and frontend based on your business logic. + + ## Step 1: Backend setup diff --git a/v2/thirdpartypasswordless/custom-ui/handling-session-tokens.mdx b/v2/thirdpartypasswordless/custom-ui/handling-session-tokens.mdx index 5c8cb0294..bdfa1a21f 100644 --- a/v2/thirdpartypasswordless/custom-ui/handling-session-tokens.mdx +++ b/v2/thirdpartypasswordless/custom-ui/handling-session-tokens.mdx @@ -9,6 +9,7 @@ hide_title: true import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; @@ -23,6 +24,7 @@ There are two modes ways in which you can use sessions with SuperTokens: Our frontend SDK uses `httpOnly` cookie based session for websites by default as it secures against tokens theft via XSS attacks. For other platform like mobile apps, we use a bearer token in the Authorization header by default. This setting can be changed [as described in the token transfer section](../common-customizations/sessions/token-transfer-method) + ## If using our frontend SDK @@ -41,9 +43,9 @@ Our frontend SDK handles everything for you. You only need to make sure that you Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. -:::note By default, our web SDKs use cookies to provide credentials. -::: + + diff --git a/v2/thirdpartypasswordless/custom-ui/securing-routes.mdx b/v2/thirdpartypasswordless/custom-ui/securing-routes.mdx index 00f7e4f61..b33427557 100644 --- a/v2/thirdpartypasswordless/custom-ui/securing-routes.mdx +++ b/v2/thirdpartypasswordless/custom-ui/securing-routes.mdx @@ -9,6 +9,7 @@ hide_title: true import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; import TabItem from '@theme/TabItem'; import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" @@ -25,6 +26,8 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" + + ### Requiring an active session For your APIs that require a user to be logged in, use the `verifySession` middleware @@ -437,7 +440,7 @@ In case of successful session verification, you get access to a `session` object ### Microservice authentication -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/introduction). +For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/unified-login/machine-to-machine-authentication). diff --git a/v2/thirdpartypasswordless/graphql-integration/backend-setup.mdx b/v2/thirdpartypasswordless/graphql-integration/backend-setup.mdx index a521f7578..ec91bb6e9 100644 --- a/v2/thirdpartypasswordless/graphql-integration/backend-setup.mdx +++ b/v2/thirdpartypasswordless/graphql-integration/backend-setup.mdx @@ -9,11 +9,14 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Backend Setup ## Complete Quick Setup + + @@ -30,6 +33,7 @@ Follow the [frontend and backend custom UI setup guides](../custom-ui/init/front + diff --git a/v2/thirdpartypasswordless/hasura-integration/with-jwt.mdx b/v2/thirdpartypasswordless/hasura-integration/with-jwt.mdx index 31e7c262c..e7a2aeb57 100644 --- a/v2/thirdpartypasswordless/hasura-integration/with-jwt.mdx +++ b/v2/thirdpartypasswordless/hasura-integration/with-jwt.mdx @@ -23,6 +23,15 @@ import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" Using SuperTokens with Hasura requires you to host your own API layer that uses our Backend SDK. If you do not want to host your own server you can use a serverless environment (AWS Lambda for example) to achieve this. ::: +:::caution + +This guide will only work if you are using **SuperTokens Session Tokens**. + +If you are implementing an **OAuth2** based feature, like **Microservice Authentication** or **Unified Login**, the method of adding custom claims is different. +Please check our separate [page](/docs/unified-login/customizations/add-custom-claims-in-tokens) that shows you how to do this. + +::: + ## 1) Complete the setup guides diff --git a/v2/thirdpartypasswordless/nestjs/guide.mdx b/v2/thirdpartypasswordless/nestjs/guide.mdx index db0c9217b..3cb76ce93 100644 --- a/v2/thirdpartypasswordless/nestjs/guide.mdx +++ b/v2/thirdpartypasswordless/nestjs/guide.mdx @@ -12,6 +12,7 @@ import BackendDeliveryMethod from "../../passwordless/reusableMD/backendDelivery import AppInfoForm from "/src/components/appInfoForm" import CoreInjector from "/src/components/coreInjector" import {Question, Answer} from "/src/components/question" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # NestJS Integration Guide @@ -432,6 +433,8 @@ bootstrap(); ## Add a session verification guard + + Now that the library is set up, you can add a guard to protect your API. You can scaffold this by running: `nest g guard auth`. In the newly created `auth.guard.ts` file, implement session verification: diff --git a/v2/thirdpartypasswordless/nextjs/app-directory/protecting-route.mdx b/v2/thirdpartypasswordless/nextjs/app-directory/protecting-route.mdx index e636f81f0..311b02a7b 100644 --- a/v2/thirdpartypasswordless/nextjs/app-directory/protecting-route.mdx +++ b/v2/thirdpartypasswordless/nextjs/app-directory/protecting-route.mdx @@ -9,16 +9,21 @@ hide_title: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import CoreInjector from "/src/components/coreInjector" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 4. Checking for sessions in frontend routes + + Protecting a website route means that it cannot be accessed unless a user is signed in. If a non signed in user tries to access it, they will be redirected to the login page. + + ## Sessions with Client Components Lets create a client component for the `/` route of our website. diff --git a/v2/thirdpartypasswordless/nextjs/app-directory/session-verification-middleware.mdx b/v2/thirdpartypasswordless/nextjs/app-directory/session-verification-middleware.mdx index a76ddce0d..4e4742164 100644 --- a/v2/thirdpartypasswordless/nextjs/app-directory/session-verification-middleware.mdx +++ b/v2/thirdpartypasswordless/nextjs/app-directory/session-verification-middleware.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # Using the Next.js middleware + + :::important This method is an alternative method for using sessions in an API. If you are already using [session guards](./session-verification-session-guard.mdx), you can skip this step. ::: diff --git a/v2/thirdpartypasswordless/nextjs/app-directory/session-verification-session-guard.mdx b/v2/thirdpartypasswordless/nextjs/app-directory/session-verification-session-guard.mdx index fb03ca4e0..663193a00 100644 --- a/v2/thirdpartypasswordless/nextjs/app-directory/session-verification-session-guard.mdx +++ b/v2/thirdpartypasswordless/nextjs/app-directory/session-verification-session-guard.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # Adding a session guard to each API route + + :::note This is applicable for when the frontend calls an API in the `/app/api` folder. ::: diff --git a/v2/thirdpartypasswordless/nextjs/app-directory/setting-up-backend.mdx b/v2/thirdpartypasswordless/nextjs/app-directory/setting-up-backend.mdx index b06c3ac05..c90c6f5e5 100644 --- a/v2/thirdpartypasswordless/nextjs/app-directory/setting-up-backend.mdx +++ b/v2/thirdpartypasswordless/nextjs/app-directory/setting-up-backend.mdx @@ -11,6 +11,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import AppInfoForm from "/src/components/appInfoForm" + # 3. Adding auth APIs We will add all the backend APIs for auth on `/api/auth`. This can be changed by setting the `apiBasePath` property in the `appInfo` object in the `appInfo.ts` file. For the rest of this page, we will assume you are using `/api/auth`. @@ -37,7 +38,7 @@ import { ensureSuperTokensInit } from '../../../config/backend'; ensureSuperTokensInit(); -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); export async function GET(request: NextRequest) { const res = await handleCall(request); diff --git a/v2/thirdpartypasswordless/nextjs/protecting-route.mdx b/v2/thirdpartypasswordless/nextjs/protecting-route.mdx index 21505368e..6de7c2164 100644 --- a/v2/thirdpartypasswordless/nextjs/protecting-route.mdx +++ b/v2/thirdpartypasswordless/nextjs/protecting-route.mdx @@ -9,9 +9,12 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" +import { OAuthFrontendVerificationDisclaimer }from "/src/components/OAuthDisclaimer" # 4. Protecting a website route + + diff --git a/v2/thirdpartypasswordless/nextjs/session-verification/in-api.mdx b/v2/thirdpartypasswordless/nextjs/session-verification/in-api.mdx index e0d57c781..182b4d5d2 100644 --- a/v2/thirdpartypasswordless/nextjs/session-verification/in-api.mdx +++ b/v2/thirdpartypasswordless/nextjs/session-verification/in-api.mdx @@ -8,9 +8,13 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # 5a. Session verification in an API call + + :::note This is applicable for when the frontend calls an API in the `/pages/api` folder. ::: diff --git a/v2/thirdpartypasswordless/nextjs/session-verification/in-ssr.mdx b/v2/thirdpartypasswordless/nextjs/session-verification/in-ssr.mdx index fcdfb1c8d..4ed75df27 100644 --- a/v2/thirdpartypasswordless/nextjs/session-verification/in-ssr.mdx +++ b/v2/thirdpartypasswordless/nextjs/session-verification/in-ssr.mdx @@ -10,9 +10,12 @@ show_ui_switcher: true import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/src/components/preBuiltOrCustomUISwitcher" import CoreInjector from "/src/components/coreInjector" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 5b. Session verification in getServerSideProps + + :::note This is applicable for when verifying a session in `getServerSideProps` or `getInitialProps`. ::: diff --git a/v2/thirdpartypasswordless/pre-built-ui/enable-email-verification.mdx b/v2/thirdpartypasswordless/pre-built-ui/enable-email-verification.mdx index 033a16a9f..070f4c321 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/enable-email-verification.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/enable-email-verification.mdx @@ -20,6 +20,7 @@ import {Answer} from "/src/components/question" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs"; import TabItem from "@theme/TabItem"; +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" import NodeJSFrameworkSubTabs from "/src/components/tabs/NodeJSFrameworkSubTabs"; import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import PythonFrameworkSubTabs from "/src/components/tabs/PythonFrameworkSubTabs"; @@ -29,6 +30,8 @@ There are two modes of email verification: - `REQUIRED`: Requires that the user's email is verified before they can access your application's frontend or backend routes (that are protected with a session). - `OPTIONAL`: Adds information about email verification into the session, but leaves it up to you to enforce it on the backend and frontend based on your business logic. + + ## Step 1: Backend setup diff --git a/v2/thirdpartypasswordless/pre-built-ui/further-reading/email-verification.mdx b/v2/thirdpartypasswordless/pre-built-ui/further-reading/email-verification.mdx index 6ceea4ed2..e05b3ee5a 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/further-reading/email-verification.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/further-reading/email-verification.mdx @@ -7,6 +7,8 @@ hide_title: true +import { OAuthEmailVerificationDisclaimer }from "/src/components/OAuthDisclaimer" + # Email verification There are two modes of email verification: @@ -15,6 +17,8 @@ There are two modes of email verification: ## Information stored in session + + On sign in or sign up, SuperTokens adds information about the email verification status in the session's payload using the `EmailVerificationClaim`. It looks like this: ```json diff --git a/v2/thirdpartypasswordless/pre-built-ui/handling-session-tokens.mdx b/v2/thirdpartypasswordless/pre-built-ui/handling-session-tokens.mdx index 05314c1d7..69b80421f 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/handling-session-tokens.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/handling-session-tokens.mdx @@ -10,6 +10,7 @@ hide_title: true import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; @@ -27,9 +28,9 @@ Our frontend SDK handles everything for you. You only need to make sure that you Our SDK adds interceptors to `fetch` and `XHR` (used by `axios`) to save and add session tokens from and to the request. -:::note By default, our web SDKs use cookies to provide credentials. -::: + + diff --git a/v2/thirdpartypasswordless/pre-built-ui/securing-routes.mdx b/v2/thirdpartypasswordless/pre-built-ui/securing-routes.mdx index 22d56941e..98c6911b0 100644 --- a/v2/thirdpartypasswordless/pre-built-ui/securing-routes.mdx +++ b/v2/thirdpartypasswordless/pre-built-ui/securing-routes.mdx @@ -14,6 +14,7 @@ import TabItem from '@theme/TabItem'; import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import GoFrameworkSubTabs from "/src/components/tabs/GoFrameworkSubTabs" import {Question, Answer}from "/src/components/question" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" # Securing your API and frontend routes @@ -24,6 +25,8 @@ import RRDVersionSubTabs from "/src/components/tabs/RRDVersionSubTabs" + + ### Requiring an active session For your APIs that require a user to be logged in, use the `verifySession` middleware @@ -436,7 +439,7 @@ In case of successful session verification, you get access to a `session` object ### Microservice authentication -For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/microservice_auth/introduction). +For authentication between microservices on your backend, checkout the [microservice auth guide](/docs/unified-login/machine-to-machine-authentication). @@ -446,6 +449,14 @@ For authentication between microservices on your backend, checkout the [microser +:::caution + +These instructions only apply to scenarios in which you are using **SuperTokens Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction) with **OAuth2 Access Tokens**, please check the [specific use case page](/docs/unified-login/introduction#when-to-use-unified-login) for relevant information. + +::: + diff --git a/v2/thirdpartypasswordless/serverless/with-aws-lambda/authorizer.mdx b/v2/thirdpartypasswordless/serverless/with-aws-lambda/authorizer.mdx index 02a71860c..b730b2d4f 100644 --- a/v2/thirdpartypasswordless/serverless/with-aws-lambda/authorizer.mdx +++ b/v2/thirdpartypasswordless/serverless/with-aws-lambda/authorizer.mdx @@ -10,9 +10,12 @@ hide_title: true import AppInfoForm from "/src/components/appInfoForm" import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Using Lambda Authorizers + + You can use a lambda as an Authorizer in API Gateways. This will enable you to use SuperTokens in a lambda to authorize requests to other integrations (e.g., AppSync). An Authorizer pointed to this lambda will add `context.authorizer.principalId` that you can map to a header. For example, you can map this to an "x-user-id" header which will be set to the id of the logged-in user. If there is no valid session for the request, this header won't exist. ## 1) Add configurations and dependencies diff --git a/v2/thirdpartypasswordless/serverless/with-aws-lambda/jwt-authorizer.mdx b/v2/thirdpartypasswordless/serverless/with-aws-lambda/jwt-authorizer.mdx index 86055a1d9..14f128558 100644 --- a/v2/thirdpartypasswordless/serverless/with-aws-lambda/jwt-authorizer.mdx +++ b/v2/thirdpartypasswordless/serverless/with-aws-lambda/jwt-authorizer.mdx @@ -16,7 +16,14 @@ import TabItem from '@theme/TabItem'; # Using JWT Authorizers :::caution + AWS supports JWT authorizers for HTTP APIs and not REST APIs on the API Gateway service. For REST APIs follow the [Lambda authorizer](./authorizer) guide + +This guide will work if you are using **SuperTokens Session Tokens**. + +If you implementing an **OAuth2** setup, through the [**Unified Login**](/docs/unified-login/introduction) or the [**Microservice Authentication**](/docs/microservice_auth/client-credentials) features, you will have to manually set the token audience property. +Please check the referenced pages for more information. + ::: ## 1) Add the `aud` claim in the JWT based on the authorizer configuration diff --git a/v2/thirdpartypasswordless/serverless/with-aws-lambda/session-verification.mdx b/v2/thirdpartypasswordless/serverless/with-aws-lambda/session-verification.mdx index 02857b5e7..ae4954e93 100644 --- a/v2/thirdpartypasswordless/serverless/with-aws-lambda/session-verification.mdx +++ b/v2/thirdpartypasswordless/serverless/with-aws-lambda/session-verification.mdx @@ -9,9 +9,12 @@ hide_title: true import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs"; import TabItem from '@theme/TabItem'; +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # 3. Session verification / Building your APIs + + When building your own APIs, you may need to verify the session of the user before proceeding further. SuperTokens SDK exposes a `verifySession` function that can be utilized for this. In this guide, we will be creating a `/user` `GET` route that will return the current session information. ## 1) Add `/user` `GET` route in your API Gateway diff --git a/v2/thirdpartypasswordless/serverless/with-netlify/session-verification.mdx b/v2/thirdpartypasswordless/serverless/with-netlify/session-verification.mdx index 1fd4b1a1f..b28b7cd62 100644 --- a/v2/thirdpartypasswordless/serverless/with-netlify/session-verification.mdx +++ b/v2/thirdpartypasswordless/serverless/with-netlify/session-verification.mdx @@ -7,8 +7,12 @@ hide_title: true +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" + # 4. Session verification / Building your APIs + + For this guide, we will assume that we want an API `/.netlify/functions/user GET` which returns the current session information. ## 1) Create a new file `netlify/functions/user.js` diff --git a/v2/thirdpartypasswordless/user-roles/protecting-routes.mdx b/v2/thirdpartypasswordless/user-roles/protecting-routes.mdx index 9d497d1ca..a970a8ab3 100644 --- a/v2/thirdpartypasswordless/user-roles/protecting-routes.mdx +++ b/v2/thirdpartypasswordless/user-roles/protecting-routes.mdx @@ -19,9 +19,19 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting API and frontend routes +:::caution + +This guide applies to scenarios which involve **SuperTokens Session Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), that uses **OAuth2 Access Tokens**, please check our [separate page](/docs/unified-login/customizations/verify-tokens) that shows you how to validate them. +You will have to check for the `roles` claim in the token payload. + +::: + ## Protecting API routes In your API routes you: diff --git a/v2/unified-login/customizations/add-custom-claims-in-tokens.mdx b/v2/unified-login/customizations/add-custom-claims-in-tokens.mdx new file mode 100644 index 000000000..7c63782cd --- /dev/null +++ b/v2/unified-login/customizations/add-custom-claims-in-tokens.mdx @@ -0,0 +1,122 @@ +--- +title: Add Custom Claims in Tokens +hide_title: true +--- + +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" +import TabItem from '@theme/TabItem'; +import OAuthPaidBanner from '../../community/reusableMD/oauth-paid-banner.mdx' + + + +# Add custom claims in the tokens + +If you want to add custom properties in the token payloads you can do this by using overrides + + +## Override the OAuth2 Access Token + + + + + + +```tsx +import OAuth2Provider from "supertokens-node/recipe/oauth2provider"; + +OAuth2Provider.init({ + override: { + functions: (originalImplementation) => ({ + ...originalImplementation, + buildAccessTokenPayload: async (input) => { + const addedInfo: Record = {}; + if (input.scopes.includes("profile")) { + addedInfo.profile = "custom-value"; + } + return { + ...(await originalImplementation.buildAccessTokenPayload(input)), + ...addedInfo, + }; + }, + }), + }, +}); + +``` + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Go SDK. + +::: + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Python SDK. + +::: + + + + + +## Override the ID Token + + + + + +```tsx +import OAuth2Provider from "supertokens-node/recipe/oauth2provider"; + +OAuth2Provider.init({ + override: { + functions: (originalImplementation) => ({ + ...originalImplementation, + buildIdTokenPayload: async (input) => { + const addedInfo: Record = {}; + if (input.scopes.includes("profile")) { + addedInfo.profile = "custom-value"; + } + return { + ...(await originalImplementation.buildIdTokenPayload(input)), + ...addedInfo, + }; + }, + }), + }, +}); + +``` + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Go SDK. + +::: + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Python SDK. + +::: + + + + diff --git a/v2/unified-login/customizations/custom-ui.mdx b/v2/unified-login/customizations/custom-ui.mdx new file mode 100644 index 000000000..088a5e052 --- /dev/null +++ b/v2/unified-login/customizations/custom-ui.mdx @@ -0,0 +1,310 @@ +--- +title: Implement a Custom UI +hide_title: true +--- + +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import CoreInjector from "/src/components/coreInjector" +import AppInfoForm from "/src/components/appInfoForm" +import TabItem from '@theme/TabItem'; +import OAuthPaidBanner from '../../community/reusableMD/oauth-paid-banner.mdx' + + + +# Implement a Custom UI + +This guide will show you how to implement the **Unified Login** functionalities using your own custom UI. + +:::info + +The following instructions assume that you already have a custom UI setup in your current project. + +If you have skipped this step, please check our [guides](/docs/guides) based on the authentication methods that you want to use. + +::: + +Before going into the actual instructions we should first recap how the **OAuth2** login flow will work. +This way we will have an easier time understanding what we are doing. + +#### 1. A user accesses your application and tries to login. + +It's up to you how you want to handle this. +They can click a button to login or you can directly start the login flow. + +#### 2. They get redirected to the **Authorization Service Backend** + +This action can be done by a **OAuth2/OIDC** library. +Check the previous guides for information on what you could use. + +#### 3. The **Authorization Service Backend** redirects them to the **Authorization Service Frontend** login page. + +The page URL will contain a `loginChallenge` parameter that will keep track of the login attempt. +Besides that, the URL can also include a `forceFreshAuth` parameter. +As the name suggests, this should force the login UI to be visible even though the user has an existing valid session. +We will show you how to handle this in the actual guide. + +#### 4. The **Authorization Service Frontend** renders the login UI and the user performs the login action. + +The login UI should render based on instructions that are specific to each authentication method which you are using. +The additional thing that you have to do here is to take into account the `forceFreshAuth` parameter. + +#### 5. The **Authorization Service Frontend** redirects the user back to the **Authorization Service Backend** + +After the user submits the login form you will have to redirect them to a specific route that will send them to the original application. +From here the authentication flow can be considered finalized. + +Now let's see how we can actually implement this UI. + +## 1. Configure the Redirection URLs + + + + + +As we have hinted in the previous section, the **Authorization Service Backend** sends the user to different pages from the **Authorization Service Frontend**, based on the action that needs to be made. + +The default values for these routes are: + +- The login page is mapped to `^{form_websiteDomain}^{form_websiteBasePath}` (this is also the place where a user ends up after logout) +- The token refresh page is mapped to `^{form_websiteDomain}^{form_websiteBasePath}/try-refresh` +- The logout page is mapped to `^{form_websiteDomain}^{form_websiteBasePath}/logout` + +If you want to change these routes you will have to add a custom override. + +:::info +This override needs to be added to the **Authorization Service Backend**. +::: + + + + + +```tsx +import OAuth2Provider from "supertokens-node/recipe/oauth2provider"; + +OAuth2Provider.init({ + override: { + functions: (originalFunctions) => ({ + ...originalFunctions, + getFrontendRedirectionURL: async (input) => { + const websiteDomain = '^{form_websiteDomain}'; + const websiteBasePath = '^{form_websiteBasePath}'; + + if (input.type === "login") { + const queryParams = new URLSearchParams({ + loginChallenge: input.loginChallenge, + }); + if (input.hint !== undefined) { + queryParams.set("hint", input.hint); + } + if (input.forceFreshAuth) { + queryParams.set("forceFreshAuth", "true"); + } + + return `${websiteDomain}${websiteBasePath}?${queryParams.toString()}`; + } else if (input.type === "try-refresh") { + return `${websiteDomain}${websiteBasePath}/try-refresh?loginChallenge=${input.loginChallenge}`; + } else if (input.type === "post-logout-fallback") { + return `${websiteDomain}${websiteBasePath}`; + } else if (input.type === "logout-confirmation") { + return `${websiteDomain}${websiteBasePath}/oauth/logout?logoutChallenge=${input.logoutChallenge}`; + } + + return `${websiteDomain}${websiteBasePath}`; + }, + }), + }, +}) + +``` + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Go SDK. + +::: + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Python SDK. + +::: + + + + + +## 2. Handle the forceFreshAuth parameter + +In some cases, even though there is an existing valid session in the **Authorization Service Frontend**, the requesting **Client** might force a new login attempt. +This is signaled by the `forceFreshAuth` parameter. + +When the login page is rendered you will also have to check for this parameter. You are doing this to know if you need to show the login UI. + +Here is an example of how you can evaluate this case. + +```tsx +import Session from 'supertokens-web-js/recipe/session'; + +async function shouldLogin() { + const urlParams = new URLSearchParams(window.location.search); + const forceFreshAuth = urlParams.get('forceFreshAuth') as string; + if(forceFreshAuth === "true") return true; + + return Session.doesSessionExist(); +} + +``` + +:::info Multi Tenancy + +If you are using multi tenancy, you will also have to keep track of the `tenantId` query parameter and pass it between the **Authorization Service Frontend** pages. + +::: + +## 3. Complete the Login Attempt + + + +After the user will submit the login form you will have to redirect them to a specific route in order to complete the **OAuth 2** flow. + +The following code sample shows you how to determine which URL to use. + + + + + + +```tsx +import OAuth2Provider from "supertokens-web-js/recipe/oauth2provider"; + +async function getInitialRedirectionURL() { + const urlParams = new URLSearchParams(window.location.search); + const loginChallenge = urlParams.get('loginChallenge') as string; + const redirectionResponse = await OAuth2Provider.getRedirectURLToContinueOAuthFlow({ loginChallenge }); + if (redirectionResponse.status === "OK") { + return redirectionResponse.frontendRedirectTo; + } +} + +``` + + + + + + +:::caution + +For mobile apps you will have to reuse the web authentication flow. Check this [guide](/docs/unified-login/reuse-website-login) for more information. + +::: + + + + + + + +## 4. Add the Token Refresh Page + +In order to have support for token refreshing you will need to add a new page to your application. +The path should correspond to the one that was outlined during the first step. + +When the user ends up on this page, you will have to use the `Session` recipe to perform the refresh action. +Then they need to be redirected to a page from your application. + +Here's a code sample that shows you how to do this. + + + + + +```tsx +import OAuth2Provider from "supertokens-web-js/recipe/oauth2provider"; +import Session from 'supertokens-web-js/recipe/session'; + +async function refreshToken() { + await Session.attemptRefreshingSession(); + const urlParams = new URLSearchParams(window.location.search); + const loginChallenge = urlParams.get('loginChallenge') as string; + const redirectionResponse = await OAuth2Provider.getRedirectURLToContinueOAuthFlow({ loginChallenge }); + if (redirectionResponse.status === "OK") { + window.location.href = redirectionResponse.frontendRedirectTo; + } +} + +``` + + + + + +:::caution + +For mobile apps you will have to reuse the web authentication flow. Check this [guide](/docs/unified-login/reuse-website-login) for more information. + +::: + + + + + +## 4. Add the Logout Page + +You will need to add a logout page that will be accessed when a user wants to end their session. +The path should correspond to the one that was outlined during the first step. + +The logout action should first ask the user for confirmation. +If the confirmation is passed then you can call the recipe function. +Based on the final response you can redirect the user to the provided redirection URL. + + + + + +```tsx +import OAuth2Provider from "supertokens-web-js/recipe/oauth2provider"; + +async function logout() { + const confirmation = confirm("Are you sure that you want to log out?"); + if(!confirmation) return; + + const urlParams = new URLSearchParams(window.location.search); + const logoutChallenge = urlParams.get('logoutChallenge') as string; + const redirectResponse = await OAuth2Provider.logOut({ logoutChallenge }); + window.location.href = redirectResponse.frontendRedirectTo; +} +``` + + + + + +:::caution + +For mobile apps you will have to reuse the web authentication flow. Check this [guide](/docs/unified-login/reuse-website-login) for more information. + +::: + + + + + + + + + diff --git a/v2/unified-login/customizations/multi-tenancy.mdx b/v2/unified-login/customizations/multi-tenancy.mdx new file mode 100644 index 000000000..625a6fa0f --- /dev/null +++ b/v2/unified-login/customizations/multi-tenancy.mdx @@ -0,0 +1,22 @@ +--- +title: Multi Tenancy +hide_title: true +--- + +import OAuthPaidBanner from '../../community/reusableMD/oauth-paid-banner.mdx' + + + +# Multi Tenancy + +If you are using a **Multi-Tenant** configuration and you are also implementing your own **custom UI**, you will have to add a tenant discovery process in your authentication flow. +This information is discussed at large in different pages of the documentation. Follow a specific guide, based on the authentication method that you are using, for more information: + +- [Email/Password](/docs/emailpassword/introduction) +- [Passwordless](/docs/passwordless/introduction) +- [Thrid Party](/docs/thirdparty/introduction) + +One thing to point out here, in the context of **OAuth2**, is that you will have to account for **Clients** passing information about which tenant is being used. +This is done through the the authorization URL, in the `tenant_id` query parameter. +The login page must handle the **tenant ID** and ensure it's being passed across different authentication pages through the URL. + diff --git a/v2/unified-login/customizations/verify-tokens.mdx b/v2/unified-login/customizations/verify-tokens.mdx new file mode 100644 index 000000000..8ee1f38c0 --- /dev/null +++ b/v2/unified-login/customizations/verify-tokens.mdx @@ -0,0 +1,427 @@ +--- +title: Verify Tokens +hide_title: true +--- + +import OAuthBackendTabs from "/src/components/tabs/OAuthBackendTabs" +import CoreInjector from "/src/components/coreInjector" +import AppInfoForm from "/src/components/appInfoForm" +import TabItem from '@theme/TabItem'; +import OAuthPaidBanner from '../../community/reusableMD/oauth-paid-banner.mdx' + + + + +# Verify Tokens + +There are two ways with which you can verify an **OAuth2 Access Token**: +- By using a standard **JWT**, JSON Web Token, verification library +- By calling the special introspection endpoint that we expose through the core service + +We will explain when you should use each method in the sections below. + +One thing to note is that, besides the standard **OAuth2** token claims, our implementation includes an additional one called `stt`. +This stands for `SuperTokens Token Type`. +It is used to make sure that the validation is performed for the correct token type: +- `0` represents a **SuperTokens Session Access Token** +- `1` represents an **OAuth2 Access Token** +- `2` represents an **OAuth2 ID Token**. + +:::info + +The following guide covers only **OAuth2 Tokens** verification. +For information on how to verify **Supertokens Session Tokens** please refer to the [following section](/docs/session/common-customizations/sessions/session-verification-in-api/overview). + +::: + + +## Validation Method + +### Using a generic JWT verification library + +This is the standard validation method and should be used for most of the operations that need to be protected. +It will tell you that the token is valid from a cryptographic standpoint and that it has not expired. + +The code samples show you a basic validation scenario where we check if the token has the required scope in order for an action to be performed. + +:::info + +If your scenario involves a common backend with multiple frontend clients you can drop the `client_id` check. + +::: + + + + + + + + + +For NodeJS you can use [jose](https://github.com/panva/jose) to verify the token. + +```tsx +import jose from "jose"; + +const JWKS = jose.createRemoteJWKSet(new URL('^{form_apiDomain}^{form_apiBasePath}jwt/jwks.json')) + +// Follow this example if you are using the Authorization Code Flow +async function validateToken(jwt: string) { + const requiredScope = ""; + const clientId = ''; + + try { + const { payload } = await jose.jwtVerify(jwt, JWKS, { + requiredClaims: ['stt', 'scp', 'client_id'], + }); + + if(payload.stt !== 1) return false; + if(payload.client_id !== clientId) return false; + + const scopes = payload.scp as string[]; + return scopes.includes(requiredScope); + } catch (err) { + return false; + } +} + + +``` + + + + + + +You can use the [jwx](https://github.com/lestrrat-go/jwx) library to verify the token. + +```go +import ( + "context" + "fmt" + + "github.com/lestrrat-go/jwx/jwt" + "github.com/lestrrat-go/jwx/jwk" +) + +func ValidateToken(token string) bool { + apiDomain := "^{form_apiDomain}" + apiBasePath := "^{form_apiBasePath}" + clientId := "" + requiredScope := "" + + jwksURL := fmt.Sprintf("%s%sjwt/jwks.json", apiDomain, apiBasePath) + jwks, err := jwk.Fetch(context.Background(), jwksURL) + if err != nil { + return false + } + + parsedToken, err := jwt.Parse( + []byte(token), + jwt.WithKeySet(jwks), + jwt.WithClaimValue("stt", 1), + jwt.WithClaimValue("client_id", clientId), + ) + if err != nil { + return false + } + + scp, ok := parsedToken.Get("scp") + if !ok { + return false + } + + scopes, ok := scp.([]interface{}) + if !ok { + return false + } + + for _, scope := range scopes { + if scope, ok := scope.(string); ok && scope == requiredScope { + return true + } + } + + return false; +} +``` + + + + + +You can use the [PyJWT](https://github.com/jpadilla/pyjwt) library to verify the token. + +```python +import requests +import jwt +from jwt import PyJWKClient + +def validate_token(token): + api_domain = "^{form_apiDomain}" + api_base_path = "^{form_apiBasePath}" + client_id = "" + required_scope = "" + + jwks_url = f"{api_domain}{api_base_path}jwt/jwks.json" + jwks_client = PyJWKClient(jwks_url) + + try: + signing_key = jwks_client.get_signing_keys_from_jwt(token) + decoded = jwt.decode( + token, + signing_key.key, + algorithms=['RS256'], + audience=audience, + options={"require": ["stt", "client_id", "scp"]} + ) + + sst = decoded.get('sst', None) + if stt != 1: + return False + + token_client_id = decoded.get('client_id', None) + if client_id != token_client_id: + return False + + + scopes = decoded.get('scp', []) + if required_scope not in scopes: + return False + + return True + except Exception as e: + return False + +``` + + + + + +You can use the [Firebase JWT](https://github.com/firebase/php-jwt) library to verify the token. + +```php + +require 'vendor/autoload.php'; + +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +function validateToken($jwt) { + $apiDomain = "^{form_apiDomain}"; + $apiBasePath = "^{form_apiBasePath}"; + $jwksUrl = $apiDomain . $apiBasePath . '/jwt/jwks.json'; + $requiredScope = ""; + $clientId = ""; + + $jwks = json_decode(file_get_contents($jwksUrl), true); + try { + $decoded = JWT::decode($jwt, JWK::parseKeySet($jwks), 'RS256')); + if ($decoded->sst !== 1) { + return false; + } + if ($decoded->client_id !== $clientId) { + return false; + } + return in_array($requiredScope, $decoded->scp); + } catch (Exception $e) { + return false; + } +} + +``` + + + + + +You can use the [Auth0 JWT](https://github.com/auth0/java-jwt) library to verify the token. +Here is an example of how you can do that: + +```java + +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; +import com.auth0.jwt.JWTVerifier.Base; +import com.auth0.jwt.algorithms.Algorithm; + +import java.net.URL; +import java.util.Map; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.nio.charset.StandardCharsets; +import java.util.Scanner; + +public class JWTVerifier { + + private static final String JWKS_URL = "^{form_apiDomain}^{form_apiBasePath}jwt/jwks.json"; + private static final String CLIENT_ID = ""; + + private static Map fetchJWKS() throws Exception { + URL url = new URL(JWKS_URL); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + + InputStream responseStream = connection.getInputStream(); + Scanner scanner = new Scanner(responseStream, StandardCharsets.UTF_8.name()); + String responseBody = scanner.useDelimiter("\\A").next(); + scanner.close(); + + return JWT.decode(responseBody).getHeader(); + } + + public static boolean validateToken(String token) { + try { + Map jwks = fetchJWKS(); + + Algorithm algorithm = Algorithm.RSA256(jwks.get("x5c"), null); + JWTVerifier verifier = JWT.require(algorithm) + .build(); + + DecodedJWT jwt = verifier.verify(token); + if(jwt.getClaim("sst").asInt() != 1) { + return false; + } + + if(jwt.getClaim("client_id").asString() != CLIENT_ID) { + return false; + } + + List scopes = jwt.getClaim("scp").asList(); + return scopes.contains(requiredScope); + } catch (Exception e) { + return false; + } + } +} +``` + + + + + +You can use the [IdentityModel](https://github.com/IdentityModel/IdentityModel) library to verify the token. + +```csharp + +using System; +using System.Linq; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +class AuthorizationCodeTokenValidator +{ + static async Task ValidateToken(string jwtStr) + { + string apiDomain = "^{form_apiDomain}"; + string apiBasePath = "^{form_apiBasePath}"; + string clientId = ""; + string requiredScope = ""; + + HttpClient client = new HttpClient(); + var response = await client.GetStringAsync($"{apiDomain}{apiBasePath}jwt/jwks.json"); + var jwks = new JsonWebKeySet(response); + + var tokenHandler = new JwtSecurityTokenHandler(); + var validationParameters = new TokenValidationParameters + { + IssuerSigningKeys = jwks.Keys + }; + + try + { + SecurityToken validatedToken; + var principal = tokenHandler.ValidateToken(jwtStr, validationParameters, out validatedToken); + var claims = principal.Claims.ToDictionary(c => c.Type, c => c.Value); + + if (!claims.ContainsKey("stt") || claims["stt"] != "1") + { + return false; + } + + if (!claims.ContainsKey("client_id") || claims["client_id"] != clientId) + { + return false; + } + + var scopes = claims["scp"].Split(" "); + if (!scopes.Contains(requiredScope)) + { + return false; + } + + return true; + } + catch (Exception) + { + return false; + } + } +} + +``` + + + + + +#### Email Verification + +If you are using email and password based authentication, and you want to validate if the user has verified their email, you will have to check if the `email_verified` claim is true. + +### Calling the Token Introspection API + +When a user logs out, their token will be removed from the **SuperTokens Core** database. +That change will not reflect in token validation process that uses a JWT verification library. +The token will stil be valid until its expiration time. + +In order to completely make sure that the token has not been removed you can directly call the **SuperTokens Core** service. +We recommend that you perform this process for high security operations, in order to avoid the risk of a token being used by a malicious agent. + +Here is an example of how you can use this validation method: + +#### By Directly Calling the Introspection Endpoint + +```bash +# The response status should be 200 +# and the body should have an "active" property set to true +curl -X POST \ + -H "Content-Type: application/json" \ + -d '{"token": ""}' \ + "^{form_apiDomain}^{form_apiBasePath}/oauth/introspect" +``` + +#### By Using the SDK Method + +If you are using a **NodeJS** backend you can directly call the SDK. + +```tsx +import OAuth2Provider from "supertokens-node/recipe/oauth2provider"; + +async function validateToken(token: string) { + const { status } = await OAuth2Provider.validateOAuth2AccessToken( + token, + { + clientId: "", + scopes: [""], + }, + true + ); + + return status === "OK"; +} +``` + + + + + diff --git a/v2/unified-login/customizations/working-with-scopes.mdx b/v2/unified-login/customizations/working-with-scopes.mdx new file mode 100644 index 000000000..aa2d4d9f7 --- /dev/null +++ b/v2/unified-login/customizations/working-with-scopes.mdx @@ -0,0 +1,81 @@ +--- +title: Working with Scopes +hide_title: true +--- + +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" +import TabItem from '@theme/TabItem'; +import OAuthPaidBanner from '../../community/reusableMD/oauth-paid-banner.mdx' + + + +# Working with Scopes + +The allowed scopes are set during the creation process of an **OAuth2 Client**. + +By default, our **OAuth2** implementation adds the following built-in scopes: +- `email`: adds the `email`, `emails` and `email_verified` claims into the **ID Token** and the **User Info**. +- `phoneNumber`: adds the `phoneNumber`, `phoneNumbers` and `phoneNumber_verified` claims into the **ID Token** and the **User Info** +- `roles`: adds the roles claim into the **ID Token** and Access Token. + - This will contain the roles returned by `getRolesForUser` + - This only works if the `UserRoles` recipe is initialized +- `permissions`: adds the `permissions` claim into the **ID Token** and Access Token. + - This will contain the list of permissions obtained by concatenating the result of `getPermissionsForRole` for all roles returned by `getRolesForUser` + - This only works if the `UserRoles` recipe is initialized + +## Requesting Specific Scopes + +The client can request specific scopes by adding `scope` query param to the **Authorization URL**. +The requested scopes have to be a subset of what is allowed for the client, otherwise the authentication request will fail. +By default all scopes are granted to the client. + +## Overriding granted scopes + +If you want to manually modify the list of scopes that are granted to the client during the authentication flow you can do this by using overrides. + + + + + + +```tsx + +import OAuth2Provider from "supertokens-node/recipe/oauth2provider"; + +OAuth2Provider.init({ + override: { + functions: (originalFunctions) => ({ + ...originalFunctions, + getRequestedScopes: async (input) => { + const originallyRequestedScopes = await originalFunctions.getRequestedScopes(input); + const filteredScopes = originallyRequestedScopes.filter((scope) => scope !== "profile"); + return [...filteredScopes, "custom-scope"]; + }, + }), + }, +}); +``` + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Go SDK. + +::: + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Python SDK. + +::: + + + + diff --git a/v2/unified-login/introduction.mdx b/v2/unified-login/introduction.mdx new file mode 100644 index 000000000..ca93c12a1 --- /dev/null +++ b/v2/unified-login/introduction.mdx @@ -0,0 +1,173 @@ +--- +title: Introduction +hide_title: true +--- + +import OAuthPaidBanner from '../community/reusableMD/oauth-paid-banner.mdx' + + + + +# Unified Login + +If your use case involves having to authenticate different types of applications using a common **Authorization Server** you can achieve this by using **SuperTokens**. + +The implementation will involve configuring an **OAuth2 Provider** that will authenticate and authorize your existing clients. + +## When to use Unified Login + +There are a couple of common scenarios in which the **Unified Login** approach can be used. +They are outlined below and we provide separate guides for each of them. + + +- **[If you have multiple frontend clients that connect to different backends](/docs/unified-login/multiple-frontends-with-separate-backends)** +- **[If you have multiple frontend clients connecting to the same backend](/docs/unified-login/multiple-frontends-with-a-single-backend)** +- **[If you want to reuse your website login for desktop and mobile apps](/docs/unified-login/reuse-website-login)** + +For these specific instances we expose recipes that allow you to complete your setup. + +:::caution +There are a few limitations with the **OAuth2** recipes: +- It is only available for the **SuperTokens Managed Service**. This feature is not included in the **Self-Hosted** version. +- Only the `supertokens-node` backend SDK supports it as of yet. So if you are using our `golang` or `python` SDK, you will have to wait for the next releases or configure a separate NodeJS **Authorization Service**. +- We do not support magic link based login. However, you can switch to `email`/`SMS` **OTP** instead. This method offers the same level of security. +- *Step Up Authentication* is not available out of the box. You will have to use customizations in order to support the flow. + +::: + + +## OAuth2 Basics + +Before we explore the guides let's first recap some common terms and concepts that are the base of the framework. +We will use them throughout the next pages. + +**OAuth2**, Open Authorization, is an industry-standard authorization framework that enables third-party applications to obtain limited access to a user's resources without exposing their credentials. + +### Roles + +In OAuth, roles define the different responsibilities of entities involved in the process of granting and obtaining access to protected resources. +There are four roles defined in the specification: + +#### Resource Owner + +The **Resource Owner** is an entity capable of granting access to a protected resource. +In most cases, this is an actual person that uses an application. + +#### Client + +An **OAuth 2.0 Client** is an application that wants to access protected resources. +In order to do so it needs to get an [**OAuth2 Access Token**](#oauth2-access-token) from the [**Authorization Server**](#authorization-server). +With that token the client can perform authorized operations on behalf of the [**Resource Owner**](#resource-owner). + +The term **client** does not imply any particular implementation characteristics (e.g. whether the application executes on a server, a desktop, or other devices). + +#### Resource Server + +The server hosting the protected resources, capable of accepting and responding to protected resource requests using [**OAuth2 Access Tokens**](#oauth2-access-tokens). +Some real-world examples in this case would be things like: +- A file storage service that allows users to access only their files +- A social media application that allows users to access only posts from their friends +- A chat app that shows only messages from conversations in which the user is a participant + +#### Authorization Server + +The server issuing [**OAuth2 Access Tokens**](#oauth2-access-tokens) to the [**Client**](#client) after successfully authenticating the [**Resource Owner**](#resource-owner). + +### Tokens + +Tokens are strings that represent the authorization issued to the [**Client**](#client). +They are mainly used to access to protected resources, on behalf of the [**Resource Owner**](#resource-owner). +At the same time, tokens can be used to get more information about who the owner is. + + +#### OAuth2 Access Token + +This is the main token that is used to provide temporary access to protected resources. +The **OAuth2 Access Token** is meant to be read and validated only by the [**Resource Server**](#resource-server). + +:::info + +This token is different from the **SuperTokens Session Access Token**. +The latter is used in the **OAuth 2.0** authentication flows to maintain a session between the **authorization frontend** and the **authorization backend server**. +::: + +#### OAuth2 Refresh Token + +A token that can be used to get a new [**OAuth2 Access Token**](#oauth2-access-token) when the current one has expired. +Using the refresh token will not require the user to re-authenticate. + +:::info + +This token is different from the **SuperTokens Session Refresh Token**. +The latter is used in the **OAuth 2.0** authentication flows to maintain a session between the **authorization frontend** and the **authorization backend server**. +::: + +#### ID Token + +This token provides identity information about the [**Resource Owner**](#resource-owner). +Unlike [**OAuth2 Access Tokens**](/#oauth2-access-token), the **ID Tokens** are meant to be read only by the [**Client**](#client). + +### Scopes + +Scopes define the range of access that the [**Client**](#client) is requesting on behalf of the [**Resource Owner**](#resource-owner). +They specify what portions of the **Resource Owner’s** data the **Client** can access and what actions it can perform. + +For example, when a user grants a web application permission to read their email, the application might request the `email` scope. + +In a general authentication flow scopes get used in the following way: +1. When the **Client** gets created a series of scopes will be specified to the **Authorization Server** +2. The **Autorization Server** authenticates the **Resource Owner** and uses the scopes to generate an **OAuth2 Access Token**. +3. The **Resource Server** will check the scopes of the **OAuth2 Access Token** and will only allow the requested actions. + + + +### Authorization Flows + +The **OAuth 2.0** protocol defines several *flows* to accommodate different use cases. +They are a set of steps an **OAuth Client** has to perform in order to obtain an access token. + +Our implementation supports the following flow types: + +#### [Authorization Code Grant](https://oauth.net/2/grant-types/authorization-code/) + +Authorization Code Grant + +This flow is best suited for scenarios that involve **web applications**. +It consists of the following steps: +1. The **Client** redirects the **Resource Owner** to the **Authorization Server’s** authorization endpoint. +2. If the **Resource Owner** grants permission, the **Authorization Server** redirects their browser back to the specified **Redirect URI** and includes an **Authorization Code** as a query parameter. +3. The **Client** then sends a request to the **Authorization Server**’s token endpoint, including the **Authorization Code**. +4. The **Authorization Server** verifies the information sent by the **Client** and, if valid, issues an **OAuth2 Access Token**. +5. The token can now be used to make requests to the **Resource Server** to access the protected resources on behalf of the **Resource Owner**. + +##### Authorization Code + +An **Authorization Code** is a short-lived code that the [**Authorization Server**](/#authorization-server) provides to the [**Client**](/#client), via a **Redirect URI**, after authorization has been granted. +This code then gets exchanged for an [**OAuth2 Access Token**](/#oauth2-access-token). +The **Authorization Code** flow enhances security by keeping tokens out of the user-agent and letting the [**Client**](/#client) manage the backend communication with the [**Authorization Server**](/#authorization-server). + +##### Proof Key for Code Exchange (PKCE) + +In order to prevent CSRF and code injection attacks, the **Authorization Code flow** can be enhanced with [**PKCE**](https://oauth.net/2/pkce/). + +At the beginning of the authentication flow the **Client** will generate a random string called a *code verifier*. +This gets used to ensure that, even if the **Authorization Code** is intercepted, it cannot be exchanged for a token without also including the initial code. + + +#### [Client Credentials](https://oauth.net/2/grant-types/client-credentials/) + +Client Credentials Grant + +This flow is best suited for **machine-to-machine** (M2M) interactions where there is no end-user. +It consists of the following steps: +1. The **Client** authenticates with the **Authorization Server** using its own credentials. +2. The **Authorization Server** verifies the credentials. +3. The **Authorization Server** returns an **OAuth2 Access Token**. +4. The **Client** uses the **OAuth2 Access Token** to access protected resources. +5. The **Resource Server** validates the **OAuth2 Access Token**. +6. If the validation is successful, the **Resource Server** returns the requested resources. + + + + + diff --git a/v2/unified-login/multiple-frontends-with-a-single-backend.mdx b/v2/unified-login/multiple-frontends-with-a-single-backend.mdx new file mode 100644 index 000000000..1ad0698bd --- /dev/null +++ b/v2/unified-login/multiple-frontends-with-a-single-backend.mdx @@ -0,0 +1,892 @@ +--- +title: Multiple frontend domains with a common backend +hide_title: true +--- + + +import NodeJSFrameworkSubTabsServerless from "/src/components/tabs/NodeJSFrameworkSubTabsServerless" +import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" +import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" +import NpmOrScriptTabs from "/src/components/tabs/NpmOrScriptTabs" +import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" +import {Question, Answer}from "/src/components/question" +import OAuthFrontendTabs from "/src/components/tabs/OAuthFrontendTabs" +import OAuthBackendTabs from "/src/components/tabs/OAuthBackendTabs" +import CoreInjector from "/src/components/coreInjector" +import AppInfoForm from "/src/components/appInfoForm" +import TabItem from '@theme/TabItem'; +import OAuthPaidBanner from '../community/reusableMD/oauth-paid-banner.mdx' + + + +# Multiple Frontend Domains with a Common Backend + +You can use the following guide if you have multiple **`frontend applications`** that use the same **`backend service`**. + +Using the actual **OAuth 2.0** terminology, we can say that each **`frontend`** can be considered a [**Client**](/docs/unified-login/introduction#client) and the **`backend`** will act as both an [**Authorization Server**](/docs/unified-login/introduction#authorization) and a [**Resource Server**](/docs/unified-login/introduction#resource-server). + +:::info + +If your frontend applications are on the same **domain**, but on different **sub-domains**, you can use [Session Sharing Across Subdomains](/docs/session/common-customizations/sessions/share-sessions-across-sub-domains) + +::: + +Multiple Frontend Domains with a Single Backend + + +The authentication flow will work in the following way: + +1. The User accesses the `frontend` app + - The application `frontend` redirects the user to [**Authorization Service**](/docs/unified-login/introduction#authorization-service) authorize URL. + - The [**Authorization Service**](/docs/unified-login/introduction#authorization-service) backend redirects the user to the login UI +2. The User completes the login attempt + - The [**Authorization Service**](/docs/unified-login/introduction#authorization-service) backend redirects the user to the `callback URL`. +3. The User accesses the callback URL + - The `frontend` uses the callback URL information to obtain a **OAuth2 Access Token** from the [**Authorization Service**](/docs/unified-login/introduction#authorization-service). + +:::info +This guide assumes that you already have setup and configured **SuperTokens** in your [**Authorization Service**](/docs/unified-login/introduction#authorization-service). + +For more information on how to do that please check our [quickstart guide](/docs/thirdparty/introduction). +::: + + + + + +## 1. Enable the OAuth2 features from the Dashboard + +You will first have to enable the **OAuth2** features from the **SuperTokens.com Dashboard**. +1. Open the **SuperTokens.com Dashboard** +2. Click on the **Enabled Paid Features** button +3. Click on **Managed Service** +4. Check the **OAuth 2.0** option +5. Click *Save* + +Now you should be able to use the OAuth2 recipes in your applications. + + + +## 2. Create the OAuth2 Clients + + + + + + +For each of your **`frontend`** applications you will have to create a separate [**OAuth2 client**](/docs/unified-login/introduction#client). +This can be done by directly calling the **SuperTokens Core** API. + +```bash +# You will have to run this for each one of your applications +# Adjust the clientName and redirectUri based on that +curl -X POST ^{coreInjector_uri_without_quotes}/recipe/oauth/clients \ + -H "Content-Type: application/json" \ + -H "api-key: ^{coreInjector_api_key_without_quotes}" \ + -d '{ + "clientName": "", + "responseTypes": ["code"], + "grantTypes": ["authorization_code", "refresh_token"], + "tokenEndpointAuthMethod": "none", + "scope": "offline_access ", + "redirectUri": ["https:///oauth/callback"], + }' +``` + +- `clientName` - A human-readable name of the client that will be used for identification. +- `responseTypes` - Specifies the types of responses your client expects from the [**Authorization Server**](/docs/unified-login/introduction#authorization-server). + - `code`: Indicates that the [**Client**](/docs/unified-login/introduction#client) will receive an **Authorization Code** that will be exchanged for an [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). +- `grantTypes` - The grant types that the [**Client**](/docs/unified-login/introduction#client) will use. + - `authorization_code`: Allows exchanging the **Authorization Code** for an [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). + - `refresh_token`: Allows exchanging the [**OAuth2 Refresh Token**](/docs/unified-login/introduction#oauth2-refresh-token) for a new [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). +- `tokenEndpointAuthMethod` - Indicates that the process of obtaining an **OAuth2 Access Token** will not make use of the client secret +- `redirectUri` - A list of URIs to which the [**Authorization Server**](/docs/unified-login/introduction#authorization-server) will send the user-agent (browser) after completing the authorization step. These can be deep links to mobile or desktop apps as well, but they must be exact URLs, without wildcards. +- `scope` - A space separated string of scopes that the [**Client**](/docs/unified-login/introduction#client) will request access to. + - `offline_access`: You need to include this scope if you want to use the [**OAuth2 Refresh Token**](/docs/unified-login/introduction#oauth2-refresh-token) to get a new [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). + + +If the creation was successful, the API will return a response that looks like this: + +```json + +{ + "clientName": "", + "clientId": "", + "callbackUrls": ["https:///oauth/callback"], +} +``` + +:::caution + +You will have to save this response because we do not persist it internally for security reasons. +In the next steps we will use the values to complete several configurations. + +::: + + + + + +### Change the default token lifespan + +By default, the tokens used in the authorization flow will have the following lifespans: + - [OAuth2 Access Token](/docs/unified-login/introduction#oauth2-access-token): 1 hour + - [OAuth2 ID Token](/docs/unified-login/introduction#oauth2-id-token): 1 hour + - [OAuth2 Refresh Token](/docs/unified-login/introduction#oauth2-refresh-token): 30 days + +If you want to change the default values you need to specify additional properties in the [**Client**](/docs/unified-login/introduction#client) creation request body. +Use string values that signify time duration in milliecoseconds, seconds, minutes or hours (e.g. `"2000ms"`, `"60s"`, `"30m"`, `"1h"`). +There are no limits on the duration of each token. + +- **OAuth2 Access Token** - Set the `authorizationCodeGrantAccessTokenLifespan` property. +- **OAuth2 ID Token** - Set the `authorizationCodeGrantIdTokenLifespan` property. +- **OAuth2 Refresh Token** - Set both the `authorizationCodeGrantRefreshTokenLifespan` and the `refreshTokenGrantRefreshTokenLifespan` properties to the same value. + + + + + + + +### Disable Refresh Token Rotation + +By default, the **OAuth2 Refresh Token** wil expire after 30 days. +If your use case cannot accomodate the process of changing the [**OAuth2 Refresh Token**](/docs/unified-login/introduction#oauth2-refresh-token) for a new one, you can make it so that this behavior does not apply for your implementation. + +In order to achieve this behavior just set the `enableRefreshTokenRotation` property to `false` in the [**Client**](/docs/unified-login/introduction#client) creation request body. + + + +## 3. Set Up your Authorization Service Backend + + +### Initialize the OAuth2 Recipes + +In your [**Authorization Service**](/docs/unified-login/introduction#authorization-service) you will need to initialize the **OAuth2Provider** recipe. + + + + +Update the `supertokens.init` call to include the new recipe. + +```tsx + +import supertokens from "supertokens-node"; +import OAuth2Provider from "supertokens-node/recipe/oauth2provider"; + +supertokens.init({ + supertokens: { + connectionURI: "...", + apiKey: "...", + }, + appInfo: { + appName: "...", + apiDomain: "...", + websiteDomain: "...", + }, + recipeList: [ + OAuth2Provider.init(), + ] +}); +``` + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Go SDK. + +::: + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Python SDK. + +::: + + + + + +### Update the CORS configuration + +You need to setup the Backend API so that it allows requests from all the frontend domains. + + + + + +```tsx +import express from "express"; +import cors from "cors"; +import supertokens from "supertokens-node"; +import { middleware } from "supertokens-node/framework/express"; + +const app = express(); + +// Add your actual frontend domains here +const allowedOrigins = ["^{form_websiteDomain}", "", ""]; + +app.use(cors({ + // highlight-start + origin: allowedOrigins, + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + credentials: true, + // highlight-end +})); + +``` + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Go SDK. + +::: + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Python SDK. + +::: + + + + + +### Implement a Custom Session Verification Function + +Given that the backend that represents the **Authorization Server** will also act as a **Resource Server** we will have to account for this in the way we verify the sessions. + +This is needed because we are using two types of tokens: +- **SuperTokens Session Access Token**: Used during the login/logout flows. +- **OAuth2 Access Token**: Used to access protected resources and perform actions that need authorization. + +Hence we need a way to distinguish between these two and prevent errors. + + + + + +Here is an example of how to implement this in the context of an Express API: + +```tsx +import supertokens from "supertokens-node"; +import Session from "supertokens-node/recipe/session"; +import express, { Request, Response, NextFunction } from 'express'; +import jose from "jose"; + +interface RequestWithUserId extends Request { + userId?: string; +} + +async function verifySession(req: RequestWithUserId, res: Response, next: NextFunction) { + let session = undefined; + try { + session = await Session.getSession(req, res, { sessionRequired: false }); + } catch (err) { + if ( + !Session.Error.isErrorFromSuperTokens(err) || + err.type !== Session.Error.TRY_REFRESH_TOKEN + ) { + return next(err); + } + } + + // In this case we are dealing with a SuperTokens Session + if (session !== undefined) { + const userId = session.getUserId(); + req.userId = userId; + return next(); + } + + // The OAuth2 Access Token needs to be manually extracted and validated + let jwt: string | undefined = undefined; + if (req.headers["authorization"]) { + jwt = req.headers["authorization"].split("Bearer ")[1]; + } + if (jwt === undefined) { + return next(new Error("No JWT found in the request")); + } + + try { + const tokenPayload = await validateToken(jwt, ''); + const userId = tokenPayload.sub; + req.userId = userId; + return next(); + } catch (err) { + return next(err); + } +} + +const JWKS = jose.createRemoteJWKSet( + new URL("^{form_apiDomain}^{form_apiBasePath}jwt/jwks.json"), +); + +// This is a basic example on how to validate an OAuth2 Token +// We have a separate page that talks more in depth about the process +async function validateToken(jwt: string, requiredScope: string) { + const { payload } = await jose.jwtVerify(jwt, JWKS, { + requiredClaims: ["stt", "scp", "sub"], + }); + + if (payload.stt !== 1) throw new Error("Invalid token"); + const scopes = payload.scp as string[]; + if (!scopes.includes(requiredScope)) throw new Error("Invalid token"); + + return payload; +} + + +// You can then use the function as a middleware for a protected route +const app = express(); +app.get("/protected", verifySession, async (req, res) => { + // Custom logic +}); + +``` + +For more information on how to verify the **OAuth2 Access Tokens** please check our [separate guide](/docs/unified-login/customizations/verify-tokens). + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Go SDK. + +::: + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Python SDK. + +::: + + + + + + +## 4. Configure the Authorization Service Frontend + +Initialize the `OAuth2Provider` recipe on the frontend of your **Authorization Service**. + +:::info + +If you want to use your own custom UI check our [separate guide](/docs/unified-login/customizations/custom-ui) that explains +all the steps that you have to take into account. + +::: + +### Initialize the Recipe + + + + + + + +Add the import statement for the new recipe and update the list of recipe to also include the new initialization. + +```tsx +import OAuth2Provider from "supertokens-auth-react/recipe/oauth2provider"; +import SuperTokens from "supertokens-auth-react"; + +SuperTokens.init({ + appInfo: { + appName: "...", + apiDomain: "...", + websiteDomain: "...", + }, + recipeList: [ + OAuth2Provider.init() + ] +}); +``` + + +#### Include the pre built UI in the rendering tree. + + + + + +```tsx +import React from 'react'; +import { + BrowserRouter, + Routes, + Route, + Link +} from "react-router-dom"; + +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +import { OAuth2ProviderPreBuiltUI } from "supertokens-auth-react/recipe/oauth2provider/prebuiltui"; +import * as reactRouterDom from "react-router-dom"; + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the /auth route*/} + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [OAuth2ProviderPreBuiltUI])} + {/*Your app routes*/} + + + + ); + } +} + +``` + + + + + +```tsx +import React from 'react'; +import { OAuth2ProviderPreBuiltUI } from "supertokens-auth-react/recipe/oauth2provider/prebuiltui"; +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; + +class App extends React.Component { + render() { + if (canHandleRoute([OAuth2ProviderPreBuiltUI])) { + // This renders the login UI on the /auth route + return getRoutingComponent([OAuth2ProviderPreBuiltUI]) + } + + return ( + {/*Your app*/} + ); + } + +} +``` + + + + + + + + + + +Update the `AuthComponent` so that it also includes the `OAuth2Provider` recipe. +You will have to add a new item in the `recipeList` array. + + + + + +```tsx title="/app/auth/auth.component.ts" + import {init as supertokensUIInit} from "supertokens-auth-react"; + import supertokensUIOAuth2Provider from "supertokens-auth-react/recipe/oauth2provider"; + import { Component, OnDestroy, AfterViewInit, Renderer2, Inject } from "@angular/core"; + import { DOCUMENT } from "@angular/common"; + + @Component({ + selector: "app-auth", + template: '
    ', + }) + export class AuthComponent implements OnDestroy, AfterViewInit { + + constructor( + private renderer: Renderer2, + @Inject(DOCUMENT) private document: Document + ) { } + + ngAfterViewInit() { + this.loadScript('^{jsdeliver_prebuiltui}'); + } + + ngOnDestroy() { + // Remove the script when the component is destroyed + const script = this.document.getElementById('supertokens-script'); + if (script) { + script.remove(); + } + } + + private loadScript(src: string) { + const script = this.renderer.createElement('script'); + script.type = 'text/javascript'; + script.src = src; + script.id = 'supertokens-script'; + script.onload = () => { + supertokensUIInit({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + // Don't forget to also include the other recipes that you are already using + supertokensUIOAuth2Provider.init() + ], + }); + } + this.renderer.appendChild(this.document.body, script); + } + } +``` + +
    + +
    + + +
    + + + + + + + +Update the `AuthView` component so that it also includes the `OAuth2Provider` recipe. +You will have to add a new item in the `recipeList` array, inside the `supertokensUIInit` call. + +```tsx + import {init as supertokensUIInit} from "supertokens-auth-react"; + import supertokensUIOAuth2Provider from "supertokens-auth-react/recipe/oauth2provider"; + + + +``` + + + + + + + + +
    + + + +### Disable Network Interceptors +The **Authorization Service Frontend** that you are configuring makes use of two types of access tokens: +- **SuperTokens Session Access Token**: Used only during the login flow in order to keep track of the authentication state. +- **OAuth2 Access Token**: Returned after a successfull login attempt. It can be used afterwards to access protected resources. + +By default, the **SuperTokens** frontend SDK will intercept all the network requests that you send to your Backend API and adjust them, based on the **SuperTokens Session Tokens**. +This allows us to perform operations, such as automatic token refreshing or adding authorization headers, without you having to configure anything else. + +Given that in the scenario that you are implementing the **OAuth2 Access Tokens** are used for authorization. +The automatic request interception will end up causing conflicts. +In order to prevent this you will have to override the `shouldDoInterceptionBasedOnUrl` function in the `Session.init` call. + +:::caution + +The code samples assume that you are using `^{form_apiBasePath}` as the `apiBasePath` for the backend authentication routes. +If that is different please adjust them based on your use case. + +::: + + + + + +```tsx +import Session from "supertokens-auth-react/recipe/session"; + +Session.init({ + override: { + functions: (oI) => { + return { + ...oI, + shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => { + try { + let urlObj = new URL(url); + // Interception should be done only for routes that need the SuperTokens Session Tokens + const isAuthApiRoute = urlObj.pathname.startsWith("^{form_apiBasePath}"); + const isOAuth2ApiRoute = urlObj.pathname.startsWith("^{form_apiBasePath}/oauth"); + if (!isAuthApiRoute || isOAuth2ApiRoute) { + return false; + } + } catch (ignored) { } + return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain); + } + } + } + } +}) +``` + + + + + +You will have to make changes to the auth route config, as well as to the `supertokens-web-js` SDK config at the root of your application: + +This change is in your auth route config. + +```tsx +// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded) +import supertokensUISession from "supertokens-auth-react/recipe/session"; + +supertokensUISession.init({ + override: { + functions: (oI) => { + return { + ...oI, + shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => { + try { + let urlObj = new URL(url); + if (!urlObj.pathname.startsWith("^{form_apiBasePath}")) { + return false; + } + } catch (ignored) { } + return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain); + } + } + } + } +}) +``` + +This change goes in the `supertokens-web-js` SDK config at the root of your application: + +```tsx +import Session from "supertokens-web-js/recipe/session"; + +Session.init({ + override: { + functions: (oI) => { + return { + ...oI, + shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => { + try { + let urlObj = new URL(url); + if (!urlObj.pathname.startsWith("^{form_apiBasePath}")) { + return false; + } + } catch (ignored) { } + return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain); + } + } + } + } +}) +``` + + + + + + +You will have to make changes to the auth route config, as well as to the `supertokens-web-js` SDK config at the root of your application: + +This change is in your auth route config. + +```tsx +// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded) +import supertokensUISession from "supertokens-auth-react/recipe/session"; + +supertokensUISession.init({ + override: { + functions: (oI) => { + return { + ...oI, + shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => { + try { + let urlObj = new URL(url); + if (!urlObj.pathname.startsWith("^{form_apiBasePath}")) { + return false; + } + } catch (ignored) { } + return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain); + } + } + } + } +}) +``` + +This change goes in the `supertokens-web-js` SDK config at the root of your application: + +```tsx +import Session from "supertokens-web-js/recipe/session"; + +Session.init({ + override: { + functions: (oI) => { + return { + ...oI, + shouldDoInterceptionBasedOnUrl: (url, apiDomain, sessionTokenBackendDomain) => { + try { + let urlObj = new URL(url); + if (!urlObj.pathname.startsWith("^{form_apiBasePath}")) { + return false; + } + } catch (ignored) { } + return oI.shouldDoInterceptionBasedOnUrl(url, apiDomain, sessionTokenBackendDomain); + } + } + } + } +}) +``` + + + + + +The code snippet only allows interception for API endpoints that start with `^{form_apiBasePath}`. +This will ensure that calls made from our Frontend SDKs will continue to use the **SuperTokens Session Tokens**. As a result, the authentication flow ends up working properly. + +For the other routes, you have full control on how you want to attach the **OAuth2 Access Tokens** to the API calls. + +## 5. Update the login flow in your frontend applications + +Use a generic OAuth2 library to handle the login flow + + + + + +We recommend using the [react-oidc-context](https://github.com/authts/react-oidc-context) library. +Just follow the instructions from the library's page. + +The configuration parameters can be determined based on the response that we received on **step 2**, when we created the **OAuth2 Client**. + +- `authority` corresponds to the endpoint of the **Authorization Service** `^{form_apiDomain}^{form_apiBasePath}` +- `clientID` corresponds to `clientId` +- `redirectUri` corresponds to a value from `callbackUrls` +- `scope` corresponds to `scope` + +If you are using a multi-tenant setup, you will also need to specify the `tenantId` parameter in the authorization URL. +To do this just set the `extraQueryParams` property with a specific value that should look like this: `{ tenant_id: "" }`. + + + + + +We recommend using the [angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc) library. +Just follow the instructions escribed [here](https://github.com/manfredsteyer/angular-oauth2-oidc?tab=readme-ov-file#logging-in). +The configuration parameters can be determined based on the response that we received on **step 2**, when we created the **OAuth2 Client**. + +- `issuer` corresponds to the endpoint of the **Authorization Service** `^{form_apiDomain}^{form_apiBasePath}` +- `client_id` corresponds to `clientId` +- `redirect_uri` corresponds to a value from `callbackUrls` +- `scope` corresponds to `scope` + +If you are using a multi-tenant setup, you will also need to specify the `tenantId` parameter in the authorization URL. +To do this just set the `customQueryParams` property with a specific value that should look like this: `{ tenant_id: "" }`. + + + + + +We recommend using the [oidc-client-ts](https://github.com/authts/oidc-client-ts?tab=readme-ov-file) library. +Just follow the instructions described [here](https://github.com/authts/oidc-client-ts/blob/main/docs/protocols/authorization-code-grant-with-pkce.md). +The configuration parameters can be determined based on the response that we received on **step 2**, when we created the **OAuth2 Client**. + + +- `issuer` corresponds to the endpoint of the **Authorization Service** `^{form_apiDomain}^{form_apiBasePath}` +- `client_id` corresponds to `clientId` +- `redirect_uri` corresponds to a value from `callbackUrls` +- `scope` corresponds to `scope` + +If you are using a multi-tenant setup, you will also need to specify the `tenantId` parameter in the authorization URL. +To do this just set the `extraQueryParams` property with a specific value that should look like this: `{ tenant_id: "" }`. + + + + + +:::info + +If you want to use the [**OAuth2 Refresh Tokens**](/docs/unified-login/introduction#oauth2-refresh-token) make sure to include the `offline_access` scope during the initialization step. + +::: + +## 6. Test the new authentication flow + +With everything set up, you can now test your login flow. +Just use the setup that you have created in the previous step to check if the authentication flow completes without any issues. + + +
    + +
    diff --git a/v2/unified-login/multiple-frontends-with-separate-backends.mdx b/v2/unified-login/multiple-frontends-with-separate-backends.mdx new file mode 100644 index 000000000..5b36c029e --- /dev/null +++ b/v2/unified-login/multiple-frontends-with-separate-backends.mdx @@ -0,0 +1,620 @@ +--- +title: Multiple frontend domains with separate backends +hide_title: true +--- + +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" +import {Question, Answer}from "/src/components/question" +import OAuthFrontendTabs from "/src/components/tabs/OAuthFrontendTabs" +import OAuthBackendTabs from "/src/components/tabs/OAuthBackendTabs" +import CoreInjector from "/src/components/coreInjector" +import AppInfoForm from "/src/components/appInfoForm" +import TabItem from '@theme/TabItem'; +import OAuthPaidBanner from '../community/reusableMD/oauth-paid-banner.mdx' + + + + +# Multiple frontend domains with separate backends + +You can use the following guide if you have a single [**Authorization Service**](/docs/unified-login/introduction#authorization-service) that is used by multiple applications. +In turn, each app has separate **`frontend `** and **`backend`** instances that are served from different domains. +Using the actual **OAuth 2.0** terminology, each application can be considered a [**Client**](/docs/unified-login/introduction#client) while the **`backends`** are also [**Resource Servers**](/docs/unified-login/introduction#resource-server). + +:::info + +Note that, if the *frontends* and *backends* are in different *sub domains*, you don't need to use *OAuth* and can instead use [session sharing across sub domains](/docs/session/common-customizations/sessions/share-sessions-across-sub-domains). + +::: + +Multiple Frontend Domains with separate Backends + +The authentication flow will work in the following way: + +1. The User accesses the `frontend` app + - The application `frontend` calls a login endpoint on the `backend` application. + - The `backend` application generates an `authorization` URL to the [**Authorization Service**](/docs/unified-login/introduction#authorization-service) and redirects the user to it. + - The [**Authorization Service**](/docs/unified-login/introduction#authorization-service) backend redirects the user to the login UI +2. The User completes the login attempt + - The [**Authorization Service**](/docs/unified-login/introduction#authorization-service) backend redirects the user to a `callback URL` that includes the **Authorization Code**. +3. The User accesses the callback URL + - The **Authentication Code** gets sent to the application `backend` + - The `backend` exchanges the **Authentication Code** for an [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token) + - The `backend` saves the received token in a server session and sends it back to the `frontend` as a cookie. + +The `frontend` can now use the new cookie to access protected resources from the `backend`. + +:::info +This guide assumes that you already have setup and configured **SuperTokens** in your [**Authorization Service**](/docs/unified-login/introduction#authorization-service). + +For more information on how to do that please check our [quickstart guides](/docs/guides). + +::: + + + + + +## 1. Enable the OAuth2 features from the Dashboard + +You will first have to enable the **OAuth2** features from the **SuperTokens.com Dashboard**. +1. Open the **SuperTokens.com Dashboard** +2. Click on the **Enabled Paid Features** button +3. Click on **Managed Service** +4. Check the **OAuth 2.0** option +5. Click *Save* + +Now you should be able to use the OAuth2 recipes in your applications. + + + +## 2. Create the OAuth2 Clients + + + + + +For each of your applications you will have to create a separate [**OAuth2 client**](/docs/unified-login/introduction#client). +This can be done by directly calling the **SuperTokens Core** API. + +```bash +# You will have to run this for each one of your applications +# Adjust the client_name and redirect_uri based on that +curl -X POST ^{coreInjector_uri_without_quotes}/recipe/oauth2/admin/clients \ + -H "Content-Type: application/json" \ + -H "api-key: ^{coreInjector_api_key_without_quotes}" \ + -d '{ + "clientName": "", + "responseTypes": ["code", "id_token"], + "grantTypes": ["authorization_code", "refresh_token"], + "scope": "offline_access ", + "redirectUri": ["https:///oauth/callback"] + }' +``` + + +- `clientName` - A human-readable name of the client that will be used for identification. +- `responseTypes` - Specifies the types of responses your client expects from the [**Authorization Server**](/docs/unified-login/introduction#authorization-server). Most of the time, you would need the following two to be present: + - `code`: Indicates that the [**Client**](/docs/unified-login/introduction#client) will receive an **Authorization Code** that will be exchanged for an [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). + - `idToken`: Indicates that the [**Client**](/docs/unified-login/introduction#client) expects an [**ID Token**](/docs/unified-login/introduction#id-token) +- `grantTypes` - The grant types that the [**Client**](/docs/unified-login/introduction#client) will use. + - `authorization_code`: Allows exchanging the **Authorization Code** for an [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). + - `refresh_token`: Allows exchanging the [**OAuth2 Refresh Token**](/docs/unified-login/introduction#oauth2-refresh-token) for a new [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). +- `redirectUri` - A list of URIs to which the [**Authorization Server**](/docs/unified-login/introduction#authorization-server) will send the user-agent (browser) after completing the authorization step. These can be deep links to mobile or desktop apps as well, but they must be exact URLs, without wildcards. +- `scope` - A space separated string of scopes that the [**Client**](/docs/unified-login/introduction#client) will request access to. + - `offline_access`: You need to include this scope if you want to use the [**OAuth2 Refresh Token**](/docs/unified-login/introduction#oauth2-refresh-token) to get a new [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). + +If the creation was successful, the API will return a response that looks like this: + +```json + +{ + "clientName": "", + "clientId": "", + "clientSecret": "", + "callbackUrls": ["https:///oauth/callback"], +} + + +``` + +Based on the client creation process we can also infer two additional values that we will need later on: +- `authorizeUrl` corresponds to `^{form_apiDomain}^{form_apiBasePath}oauth/auth` +- `tokenFetchUrl` corresponds to `^{form_apiDomain}^{form_apiBasePath}oauth/token` + +:::caution + +You will have to save this response because we do not persist it internally for security reasons. +In the next steps we will use the values to complete several configurations. + +::: + + + + + +### Change the default token lifespan + +By default, the tokens used in the authorization flow will have the following lifespans: + - [OAuth2 Access Token](/docs/unified-login/introduction#oauth2-access-token): 1 hour + - [OAuth2 ID Token](/docs/unified-login/introduction#oauth2-id-token): 1 hour + - [OAuth2 Refresh Token](/docs/unified-login/introduction#oauth2-refresh-token): 30 days + +If you want to change the default values you need to specify additional properties in the [**Client**](/docs/unified-login/introduction#client) creation request body. +Use string values that signify time duration in milliecoseconds, seconds, minutes or hours (e.g. `"2000ms"`, `"60s"`, `"30m"`, `"1h"`). +There are no limits on the duration of each token. + +- **OAuth2 Access Token** - Set the `authorizationCodeGrantAccessTokenLifespan` property. +- **OAuth2 ID Token** - Set the `authorizationCodeGrantIdTokenLifespan` property. +- **OAuth2 Refresh Token** - Set both the `authorizationCodeGrantRefreshTokenLifespan` and the `refreshTokenGrantRefreshTokenLifespan` properties to the same value. + + + + + + + + +### Disable Refresh Token Rotation + +By default, the **OAuth2 Refresh Token** wil expire after 30 days. +If your use case cannot accomodate the process of changing the [**OAuth2 Refresh Token**](/docs/unified-login/introduction#oauth2-refresh-token) for a new one, you can make it so that this behavior does not apply for your implementation. + +In order to achieve this behavior just set the `enableRefreshTokenRotation` property to `false` in the [**Client**](/docs/unified-login/introduction#client) creation request body. + + + + +## 3. Set Up your Authorization Service + +### Configure the Authorization Service Backend + +In your [**Authorization Service**](/docs/unified-login/introduction#authorization-service) you will need to initialize the **OAuth2Provider** recipe. +The recipe will expose the endpoints needed for enabling the [**OAuth 2.0**](/docs/unified-login/introduction#oauth-20) flow. + + + + + +Update the `supertokens.init` call to include the `OAuth2Provider` recipe. + +Add the import statement for the recipe and update the list of recipes with the new initialization step. + +```tsx +import supertokens from "supertokens-node"; +import OAuth2Provider from "supertokens-node/recipe/oauth2provider"; + +supertokens.init({ + supertokens: { + connectionURI: "...", + apiKey: "...", + }, + appInfo: { + appName: "...", + apiDomain: "...", + websiteDomain: "...", + }, + recipeList: [ + OAuth2Provider.init(), + ] +}); + +``` + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Go SDK. + +::: + + + + + +:::caution + +At the moment we do not have support creating OAuth2 providers in the Python SDK. + +::: + + + + + + +### Configure the Authorization Service Frontend + + +Initialize the `OAuth2Provider` recipe on the frontend of your **Authorization Service**. + +:::info + +If you want to use your own custom UI check our [separate guide](/docs/unified-login/customizations/custom-ui) that explains +all the steps that you have to take into account. + +::: + + + + + + +Add the import statement for the new recipe and update the list of recipe to also include the new initialization. + +```tsx +import OAuth2Provider from "supertokens-auth-react/recipe/oauth2provider"; +import SuperTokens from "supertokens-auth-react"; + +SuperTokens.init({ + appInfo: { + appName: "...", + apiDomain: "...", + websiteDomain: "...", + }, + recipeList: [ + OAuth2Provider.init() + ] +}); +``` + + +#### Include the pre built UI in the rendering tree. + + + + + +```tsx +import React from 'react'; +import { + BrowserRouter, + Routes, + Route, + Link +} from "react-router-dom"; + +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +import { OAuth2ProviderPreBuiltUI } from "supertokens-auth-react/recipe/oauth2provider/prebuiltui"; +import * as reactRouterDom from "react-router-dom"; + +class App extends React.Component { + render() { + return ( + + + + {/*This renders the login UI on the /auth route*/} + {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [OAuth2ProviderPreBuiltUI])} + {/*Your app routes*/} + + + + ); + } +} + +``` + + + + + +```tsx +import React from 'react'; +import { OAuth2ProviderPreBuiltUI } from "supertokens-auth-react/recipe/oauth2provider/prebuiltui"; +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; + +class App extends React.Component { + render() { + if (canHandleRoute([OAuth2ProviderPreBuiltUI])) { + // This renders the login UI on the /auth route + return getRoutingComponent([OAuth2ProviderPreBuiltUI]) + } + + return ( + {/*Your app*/} + ); + } + +} +``` + + + + + + + + + + +Update the `AuthComponent` so that it also includes the `OAuth2Provider` recipe. +You will have to add a new item in the `recipeList` array. + + + + + +```tsx title="/app/auth/auth.component.ts" + import {init as supertokensUIInit} from "supertokens-auth-react"; + import supertokensUIOAuth2Provider from "supertokens-auth-react/recipe/oauth2provider"; + import { Component, OnDestroy, AfterViewInit, Renderer2, Inject } from "@angular/core"; + import { DOCUMENT } from "@angular/common"; + + @Component({ + selector: "app-auth", + template: '
    ', + }) + export class AuthComponent implements OnDestroy, AfterViewInit { + + constructor( + private renderer: Renderer2, + @Inject(DOCUMENT) private document: Document + ) { } + + ngAfterViewInit() { + this.loadScript('^{jsdeliver_prebuiltui}'); + } + + ngOnDestroy() { + // Remove the script when the component is destroyed + const script = this.document.getElementById('supertokens-script'); + if (script) { + script.remove(); + } + } + + private loadScript(src: string) { + const script = this.renderer.createElement('script'); + script.type = 'text/javascript'; + script.src = src; + script.id = 'supertokens-script'; + script.onload = () => { + supertokensUIInit({ + appInfo: { + appName: "^{form_appName}", + apiDomain: "^{form_apiDomain}", + websiteDomain: "^{form_websiteDomain}", + apiBasePath: "^{form_apiBasePath}", + websiteBasePath: "^{form_websiteBasePath}" + }, + recipeList: [ + // Don't forget to also include the other recipes that you are already using + supertokensUIOAuth2Provider.init() + ], + }); + } + this.renderer.appendChild(this.document.body, script); + } + } +``` + +
    + +
    + + +
    + + + + + + + +Update the `AuthView` component so that it also includes the `OAuth2Provider` recipe. +You will have to add a new item in the `recipeList` array, inside the `supertokensUIInit` call. + +```tsx + import {init as supertokensUIInit} from "supertokens-auth-react"; + import supertokensUIOAuth2Provider from "supertokens-auth-react/recipe/oauth2provider"; + + + +``` + + + + + + + + +
    + + + +## 4. Set Up Session Handling in Each Application + +In each of your individual `applications` you will have to setup up logic for handling the **OAuth 2.0** authentication flow. +We recommend using a generic **OICD** or **OAuth2** library in order to do this. + + + + + +You can use the [passport-oauth2](https://www.passportjs.org/packages/passport-oauth2/) library. +Just follow the instructions on the library's page and setup your application `backend`. +The configuration parameters can be determined based on the response that we received on **step 2**, when we created the **OAuth2 Client**. + +- `authorizationURL` corresponds to `authorizeUrl` +- `tokenURL` corresponds to `tokenFetchUrl` +- `clientID` corresponds to `clientId` +- `clientSecret` corresponds to `clientSecret` +- `callbackURL` corresponds to a value from `callbackUrls` +- `scope` corresponds to `scope` + + +Make sure that you expose an endpoint that will call `passport.authenticate('oauth2')`. +This way the user will end up accessing the actual login page served by the **Authorization Service**. + + + + + +You can use the [OAuth2](https://pkg.go.dev/golang.org/x/oauth2) library. +Just follow these [instructions](https://golang.org/pkg/golang.org/x/oauth2/#example-Config-RequestToken) and implement it in your `backend`. + +The configuration parameters can be determined based on the response that we received on **step 2**. +- `ClientID` corresponds to `clientId` +- `ClientSecret` corresponds to `clientSecret` +- `Scopes` corresponds to `scope` +- `Endpoint.AuthURL` corresponds to `authorizeUrl` +- `Endpoint.TokenURL` corresponds to `tokenFetchUrl` + +Make sure that you expose an endpoint that will redirect to the authentication URL obtained from calling `AuthCodeURL`. +This way the user will end up accessing the actual login page served by the **Authorization Service**. + + + + + + +You can use the [AuthLib](https://docs.authlib.org/) library. +Just follow these [instructions](https://docs.authlib.org/en/latest/client/oauth2.html) and implement it in your `backend`. + +The configuration parameters can be determined based on the response that we received on **step 2**. +- `client_id` corresponds to `clientId` +- `client_secret` corresponds to `clientSecret` +- `scope` corresponds to `scope` +- `authorization_endpoint` corresponds to `authorizeUrl` +- `token_endpoint` corresponds to `tokenFetchUrl` + +Make sure that you expose an endpoint that will redirect to the authentication URL obtained from calling `create_authorization_url`. +This way the user will end up accessing the actual login page served by the **Authorization Service**. + + + + + + +You can use the [League OAuth2 Client](https://github.com/thephpleague/oauth2-client) library. +Just follow these [instructions](https://oauth2-client.thephpleague.com/usage/) and implement it in your `backend`. + +The configuration parameters can be determined based on the response that we received on **step 2**. +- `clientId` corresponds to `clientId` +- `clientSecret` corresponds to `clientSecret` +- `redirectUri` corresponds to a value from `callbackUrls` +- `urlAuthorize` corresponds to `authorizeUrl` +- `urlAccessToken` corresponds to `tokenFetchUrl` + +Make sure that you expose an endpoint that will redirect to the authentication URL obtained from calling `getAuthorizationUrl`. +This way the user will end up accessing the actual login page served by the **Authorization Service**. + + + + + +You can use the [Spring Security](https://github.com/spring-projects/spring-security) library. +Just follow these [instructions](https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html#oauth2-client-log-users-in) and implement it in your `backend`. + +The configuration parameters can be determined based on the response that we received on **step 2**. + +- `client-id` corresponds to `clientId` +- `client-secret` corresponds to `clientSecret` +- `scope` corresponds to `scope` +- `issuer-uri` corresponds to `^{form_apiDomain}^{form_apiBasePath}` + + + + + + +You can use the [IdentityModel](https://github.com/IdentityModel/IdentityModel) library. +Just follow these [instructions](https://identitymodel.readthedocs.io/en/latest/client/token.html#requesting-a-token-using-the-authorization-code-grant-type) and implement it in your `backend`. + +The configuration parameters can be determined based on the response that we received on **step 2**. + +- `Address` corresponds to `^{form_apiDomain}^{form_apiBasePath}` +- `ClientId` corresponds to `clientId` +- `ClientSecret` corresponds to `clientSecret` +- `RedirectUri` corresponds to a value from `callbackUrls` + + +Make sure that you expose an endpoint that will redirect to the authentication URL obtained by using [this example](https://identitymodel.readthedocs.io/en/latest/misc/request_url.html#authorization-endpoint). +This way the user will end up accessing the actual login page served by the **Authorization Service**. + + + + + +:::info + +If you want to use the [**OAuth2 Refresh Tokens**](/docs/unified-login/introduction#oauth2-refresh-token) make sure to include the `offline_access` scope during the initialization step. + +::: + +## 5. Update the login flow in your frontend applications + +In your `frontend` applications you will have to add a login action that will direct the user to the authentication page. + +The user should first be redirected to the `backend` authentication endpoint that was defined during the previous step. +There the `backend` will generate a safe `authorization` URL using the **OAuth2** library and then redirect the user there. +After the user has logged in from the [**Authorization Service**](/docs/unified-login/introduction#authorization-service) they will be redirected to the `backend` callback URL. +Then the authentication session will be created and the `backend` will send it to the user agent as a cookie. + +## 6. Test the new authentication flow + +With everything set up, you can now test your login flow. +Just use the setup that you have created in the previous step to check if the authentication flow completes without any issues. + +
    + +
    diff --git a/v2/unified-login/reuse-website-login.mdx b/v2/unified-login/reuse-website-login.mdx new file mode 100644 index 000000000..317a120de --- /dev/null +++ b/v2/unified-login/reuse-website-login.mdx @@ -0,0 +1,254 @@ +--- +title: Reuse website login for desktop and mobile apps +hide_title: true +--- + +import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs" +import {Question, Answer}from "/src/components/question" +import OAuthFrontendTabs from "/src/components/tabs/OAuthFrontendTabs" +import OAuthBackendTabs from "/src/components/tabs/OAuthBackendTabs" +import OAuthMobileTabs from "/src/components/tabs/OAuthMobileTabs" +import CoreInjector from "/src/components/coreInjector" +import AppInfoForm from "/src/components/appInfoForm" +import TabItem from '@theme/TabItem'; +import OAuthPaidBanner from '../community/reusableMD/oauth-paid-banner.mdx' + + + + +# Reuse website login for desktop and mobile apps + +This pattern is useful if you want to have the same web authentication experience for your desktop and mobile apps. +The flow will allow you to save develpoment time but you will have to keep in mind that it will not involve a native authentication interface. +Users will be directed to a separate browser page where they will complete the authentication flow. + +Reuse website login for desktop and mobile apps + +The authentication flow will work in the following way: + +1. User accesses the native application +2. The user gets redirected to [**Authorization Service**](/docs/unified-login/introduction#authorization-service) authentication url. +3. The [**Authorization Service**](/docs/unified-login/introduction#authorization-service) redirects the user to the login UI +4. User completes the login attempt +5. The [**Authorization Service**](/docs/unified-login/introduction#authorization-service) backend redirects the user to the `callback URL`. +This URL should be a deep link that can be opened by your actual application. +6. The application uses the callback URL information to request a **OAuth2 Access Token** from the [**Authorization Service**](/docs/unified-login/introduction#authorization-service). +7. The returned token is saved by the application. + +:::info +This guide assumes that you already have setup and configured **SuperTokens** in your [**Authentication Service**](/docs/unified-login/introduction#authentication-service). + +For more information on how to do that please check our [quickstart guides](/docs/guides). +::: + + + + + +## 1. Enable the OAuth2 features from the Dashboard + +You will first have to enable the **OAuth2** features from the **SuperTokens.com Dashboard**. +1. Open the **SuperTokens.com Dashboard** +2. Click on the **Enabled Paid Features** button +3. Click on **Managed Service** +4. Check the **OAuth 2.0** option +5. Click *Save* + +Now you should be able to use the OAuth2 recipes in your applications. + + + + +## 2. Create the OAuth2 Clients + + + + + +For each of your **`applications`** you will have to create a separate [**OAuth2 Client**](/docs/unified-login/introduction#client). +This can be done by directly calling the **SuperTokens Core** API. + + +```bash +# You will have to run this for each one of your applications +# Adjust the client_name and redirect_uri based on that +curl -X POST ^{coreInjector_uri_without_quotes}/recipe/oauth2/admin/clients \ + -H "Content-Type: application/json" \ + -H "api-key: ^{coreInjector_api_key_without_quotes}" \ + -d '{ + "clientName": "", + "responseTypes": ["code", "id_token"], + "grantTypes": ["authorization_code", "refresh_token"], + "tokenEndpointAuthMethod": "none", + "scope": "offline_access ", + "redirectUri": ["https:///oauth/callback"] + }' +``` + +- `clientName` - A human-readable name of the client that will be used for identification. +- `responseTypes` - Specifies the types of responses your client expects from the [**Authorization Server**](/docs/unified-login/introduction#authorization-server). Most of the time, you would need the following two to be present: + - `code`: Indicates that the [**Client**](/docs/unified-login/introduction#client) will receive an **Authorization Code** that will be exchanged for an [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). + - `id_token`: Indicates that the [**Client**](/docs/unified-login/introduction#client) expects an [**ID Token**](/docs/unified-login/introduction#id-token) +- `grantTypes` - The grant types that the [**Client**](/docs/unified-login/introduction#client) will use. + - `authorization_code`: Allows exchanging the **Authorization Code** for an [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). + - `refresh_token`: Allows exchanging the [**OAuth2 Refresh Token**](/docs/unified-login/introduction#oauth2-refresh-token) for a new [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). +- `tokenEndpointAuthMethod` - Indicates that the process of obtaining an **OAuth2 Access Token** will not make use of the client secret +- `redirectUri` - A list of URIs to which the [**Authorization Server**](/docs/unified-login/introduction#authorization-server) will send the user-agent (browser) after completing the authorization step. These can be deep links to mobile or desktop apps as well, but they must be exact URLs, without wildcards. +- `scope` - A space separated string of scopes that the [**Client**](/docs/unified-login/introduction#client) will request access to. + - `offline_access`: You need to include this scope if you want to use the [**OAuth2 Refresh Token**](/docs/unified-login/introduction#oauth2-refresh-token) to get a new [**OAuth2 Access Token**](/docs/unified-login/introduction#oauth2-access-token). + +If the creation was successful, the API will return a response that looks like this: + +```json + +{ + "clientName": "", + "clientId": "", + "callbackUrls": ["https:///oauth/callback"], +} + +``` + +Based on the client creation process we can also infer two additional values that we will need later on: +- `authorizeUrl` corresponds to `^{form_apiDomain}^{form_apiBasePath}/oauth/auth` +- `tokenFetchUrl` corresponds to `^{form_apiDomain}^{form_apiBasePath}/oauth/token` + +:::caution + +You will have to save this response because we do not persist it internally for security reasons. +In the next steps we will use the values to complete several configurations. + +::: + + + + + +### Change the default token lifespan + +By default, the tokens used in the authorization flow will have the following lifespans: + - [OAuth2 Access Token](/docs/unified-login/introduction#oauth2-access-token): 1 hour + - [OAuth2 ID Token](/docs/unified-login/introduction#oauth2-id-token): 1 hour + - [OAuth2 Refresh Token](/docs/unified-login/introduction#oauth2-refresh-token): 30 days + +If you want to change the default values you need to specify additional properties in the [**Client**](/docs/unified-login/introduction#client) creation request body. +Use string values that signify time duration in milliecoseconds, seconds, minutes or hours (e.g. `"2000ms"`, `"60s"`, `"30m"`, `"1h"`). +There are no limits on the duration of each token. + +- **OAuth2 Access Token** - Set the `authorizationCodeGrantAccessTokenLifespan` property. +- **OAuth2 ID Token** - Set the `authorizationCodeGrantIdTokenLifespan` property. +- **OAuth2 Refresh Token** - Set both the `authorizationCodeGrantRefreshTokenLifespan` and the `refreshTokenGrantRefreshTokenLifespan` properties to the same value. + + + +## 3. Make sure that you have properly configured the Authorization Service + +If you have not set up the [**Authorization Service**](/docs/unified-login/introduction#authorization-service) yet, please check one of the previous guides based whether you use multiple backend service: +- [Single Backend Setup](/docs/unified-login/multiple-frontends-with-a-single-backend#3-set-up-your-authorization-service-backend) +- [Multiple Backends Setup](/docs/unified-login/multiple-frontends-with-separate-backends#3-set-up-your-authorization-service) + + +## 4. Update the login flow in your applications + +In each of your individual `applications` you will have to setup up logic for handling the **OAuth 2.0** authentication flow. +We recommend using a generic **OICD** or **OAuth** library in order to do this. + + + + + +You can use the [react-native-app-auth](https://commerce.nearform.com/open-source/react-native-app-auth/) library. +Just follow [the instructions](https://commerce.nearform.com/open-source/react-native-app-auth/docs/usage/config) to setup your application. + +The configuration parameters can be determined based on the response that we received on **step 2**, when we created the **OAuth2 Client**. + +- `issuer` corresponds to the endpoint of the **Authorization Service** `^{form_apiDomain}^{form_apiBasePath}` +- `clientID` corresponds to `clientId` +- `redirectUrl` corresponds a value from `callbackUrls` +- `scopes` corresponds to `scopes` + +You will also need to set the `additionalParameters` property with the following values: +- `max_age: 0` This will force a new authentication flow once the user ends up on the **Authorization Service** frontend. +- `tenant_id: ` Optional, in case you are using a multi tenant setup. Set this to the actual tenant ID. + + + + + + +You can use the [AppAuth-Android](https://github.com/openid/AppAuth-Android) library. +Just follow [the instructions](https://github.com/openid/AppAuth-Android?tab=readme-ov-file#authorization-service-configuration) to setup your application. + +The configuration parameters can be determined based on the response that we received on **step 2**, when we created the **OAuth2 Client**. + +For the `AuthorizationServiceConfiguration` the parameters that you will have to provide are: `authorizeUrl` and `tokenFetchUrl`. +When calling the `AuthorizationRequest.Builder` function you can use `clientId` and a value from `callbackUrls` to replace the example values. + +You will need to set additional query parameters by calling the `setAdditionalParameters` function on the `AuthorizationRequest.Builder` object: +- `max_age: 0` This will force a new authentication flow once the user ends up on the **Authorization Service** frontend. +- `tenant_id: ` Optional, in case you are using a multi tenant setup. Set this to the actual tenant ID. + + + + + + +You can use the [AppAuth-iOS](https://github.com/openid/AppAuth-Android) library. +Just follow [the instructions](https://github.com/openid/AppAuth-iOS?tab=readme-ov-file#auth-flow) to setup your application. + +The configuration parameters can be determined based on the response that we received on **step 2**, when we created the **OAuth2 Client**. + +- `clientID` corresponds to `clientId` +- `redirectUrl` corresponds a value from `callbackUrls` +- `scopes` corresponds to `scopes` +- `authorizationEndpoint` corresponds to `authorizeUrl` +- `tokenEndpoint` corresponds to `tokenFetchUrl` + +You will also need to set extra query parameters, when instantiating the `OIDAuthorizationRequest` object, with the following values: +- `max_age: 0` This will force a new authentication flow once the user ends up on the **Authorization Service** frontend. +- `tenant_id: ` Optional, in case you are using a multi tenant setup. Set this to the actual tenant ID. + + + + + +You can use the [AppAuth](https://github.com/MaikuB/flutter_appauth) library. +Just follow [the instructions](https://github.com/MaikuB/flutter_appauth/tree/master/flutter_appauth) to setup your application. + +The configuration parameters can be determined based on the response that we received on **step 2**, when we created the **OAuth2 Client**. + +- `` corresponds to `clientId` +- `` corresponds to the endpoint of the **Authorization Service** `^{form_apiDomain}^{form_apiBasePath}` +- `` corresponds a value from `callbackUrls` +- `scopes` corresponds to `scopes` + +You will also need to set the `additionalParameters` property with the following values: +- `max_age: 0` This will force a new authentication flow once the user ends up on the **Authorization Service** frontend. +- `tenant_id: ` Optional, in case you are using a multi tenant setup. Set this to the actual tenant ID. + + + + + + +:::info + +If you want to use the [**OAuth2 Refresh Tokens**](/docs/unified-login/introduction#oauth2-refresh-token) make sure to include the `offline_access` scope during the initialization step. + +::: + + +## 5. Test the new authentication flow + +With everything set up, you can now test your login flow. +Just use the setup that you have created in the previous step to check if the authentication flow completes without any issues. + + + + + + diff --git a/v2/unified-login/sidebars.js b/v2/unified-login/sidebars.js new file mode 100644 index 000000000..4e05702c9 --- /dev/null +++ b/v2/unified-login/sidebars.js @@ -0,0 +1,27 @@ +module.exports = { + sidebar: [ + "introduction", + { + type: "category", + label: "Use Cases", + collapsed: false, + items: [ + "multiple-frontends-with-separate-backends", + "multiple-frontends-with-a-single-backend", + "reuse-website-login", + ], + }, + { + type: "category", + label: "Customizations", + collapsed: false, + items: [ + "customizations/working-with-scopes", + "customizations/add-custom-claims-in-tokens", + "customizations/verify-tokens", + "customizations/custom-ui", + "customizations/multi-tenancy", + ], + }, + ], +}; diff --git a/v2/userroles/protecting-routes.mdx b/v2/userroles/protecting-routes.mdx index 83374c6ce..a970a8ab3 100644 --- a/v2/userroles/protecting-routes.mdx +++ b/v2/userroles/protecting-routes.mdx @@ -19,9 +19,19 @@ import {PreBuiltOrCustomUISwitcher, PreBuiltUIContent, CustomUIContent} from "/s import FrontendPreBuiltUITabs from "/src/components/tabs/FrontendPreBuiltUITabs" import FrontendCustomUITabs from "/src/components/tabs/FrontendCustomUITabs" import FrontendMobileSubTabs from "/src/components/tabs/FrontendMobileSubTabs" +import { OAuthVerifyTokensDisclaimer }from "/src/components/OAuthDisclaimer" # Protecting API and frontend routes +:::caution + +This guide applies to scenarios which involve **SuperTokens Session Access Tokens**. + +If you are implementing [**Unified Login**](/docs/unified-login/introduction), that uses **OAuth2 Access Tokens**, please check our [separate page](/docs/unified-login/customizations/verify-tokens) that shows you how to validate them. +You will have to check for the `roles` claim in the token payload. + +::: + ## Protecting API routes In your API routes you: @@ -1546,4 +1556,4 @@ Future checkIfUserIsAnAdmin() async { -
    \ No newline at end of file +